| /**************************************************************************** |
| * arch/xtensa/src/esp32/esp32_emac.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| #if defined(CONFIG_ESP32_EMAC) |
| |
| #include <stdint.h> |
| #include <string.h> |
| #include <stdlib.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <errno.h> |
| |
| #include <arpa/inet.h> |
| #include <net/if.h> |
| |
| #include <nuttx/crc8.h> |
| #include <nuttx/wdog.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/list.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/net/ioctl.h> |
| #include <nuttx/net/net.h> |
| #include <nuttx/net/mii.h> |
| #include <nuttx/net/netdev.h> |
| |
| #ifdef CONFIG_NET_PKT |
| # include <nuttx/net/pkt.h> |
| #endif |
| |
| #include "xtensa.h" |
| #include "xtensa_attr.h" |
| |
| #include "hardware/esp32_gpio_sigmap.h" |
| #include "hardware/esp32_dport.h" |
| #include "hardware/esp32_emac.h" |
| #include "esp32_gpio.h" |
| #include "esp32_irq.h" |
| |
| #include <arch/board/board.h> |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* Configuration ************************************************************/ |
| |
| #define ETH_HEADER_LEN (14) /* Ethernet frame header length */ |
| #define ETH_VLANTAG_LEN (4) /* Ethernet frame VLAN TAG length */ |
| #define ETH_MAXPAYLOAD_LEN (1500) /* Ethernet frame payload length */ |
| #define ETH_CRC_LEN (4) /* Ethernet frame CRC32 length */ |
| |
| /* Ethernet packet buffer maximum size */ |
| |
| #define ETH_MAXPKT_LEN (ETH_HEADER_LEN + ETH_VLANTAG_LEN + \ |
| ETH_MAXPAYLOAD_LEN + ETH_CRC_LEN) |
| |
| /* RX/TX buffer size */ |
| |
| #define EMAC_BUF_LEN (ETH_MAXPKT_LEN) |
| |
| /* RX/TX buffer number */ |
| |
| #define EMAC_RX_BUF_NUM (CONFIG_ESP32_ETH_NRXDESC) |
| #define EMAC_TX_BUF_NUM (CONFIG_ESP32_ETH_NTXDESC) |
| |
| /* Total buffer number */ |
| |
| #define EMAC_BUF_NUM (EMAC_RX_BUF_NUM + EMAC_TX_BUF_NUM + 1) |
| |
| /* Read/Write/Reset PHY delays in loop counts, unit is 100 us */ |
| |
| #define EMAC_READPHY_TO (10 * 10) |
| #define EMAC_WRITEPHY_TO (10 * 10) |
| #define EMAC_RSTPHY_TO (10 * 100) |
| |
| /* Soft reset EMAC delays in loop counts, unit is 10 us */ |
| |
| #define EMAC_RESET_TO (100 * 1000) |
| |
| /* Wait ethernet linked in loop counts, unit is 10 us */ |
| |
| #define EMAC_WAITLINK_TO (100 * 100) |
| |
| /* Transmit ethernet frame timeout = 60 seconds = 1 minute */ |
| |
| #define EMAC_TX_TO (60 * CLK_TCK) |
| |
| /* Ethernet control frame pause timeout */ |
| |
| #define EMAC_PAUSE_TIME (0x1648) |
| |
| /* Ethernet stop frame PT-28 */ |
| |
| #define EMAC_PLT_TYPE (1) |
| |
| /* SMI clock value */ |
| |
| #define EMAC_SMI_CLK (4) |
| |
| /* DMA transmission number of beats */ |
| |
| #define EMAC_DMARXPBL_NUM (32) |
| #define EMAC_DMATXPBL_NUM (32) |
| |
| /* RMII interface pins, each pin has fixed number */ |
| |
| #define EMAC_ICLK_PIN (0) |
| #define EMAC_TXEN_PIN (21) |
| #define EMAC_TXDO_PIN (19) |
| #define EMAC_TXD1_PIN (22) |
| #define EMAC_RXDO_PIN (25) |
| #define EMAC_RXD1_PIN (26) |
| #define EMAC_RXDV_PIN (27) |
| |
| /* SMI interface pins */ |
| |
| #define EMAC_MDC_PIN (CONFIG_ESP32_ETH_MDCPIN) |
| #define EMAC_MDIO_PIN (CONFIG_ESP32_ETH_MDIOPIN) |
| |
| /* Reset PHY chip pins */ |
| |
| #ifdef CONFIG_ESP32_ETH_ENABLE_PHY_RSTPIN |
| # define EMAC_PHYRST_PIN (CONFIG_ESP32_ETH_PHY_RSTPIN) |
| #endif |
| |
| /* PHY chip address in SMI bus */ |
| |
| #define EMAC_PHY_ADDR (CONFIG_ESP32_ETH_PHY_ADDR) |
| |
| /* The low priority work queue is preferred. If it is not enabled, LPWORK |
| * will be the same as HPWORK. |
| * |
| * NOTE: However, the network should NEVER run on the high priority work |
| * queue! That queue is intended only to service short back end interrupt |
| * processing that never suspends. Suspending the high priority work queue |
| * may bring the system to its knees! |
| */ |
| |
| #define EMACWORK LPWORK |
| #define ETHWORK LPWORK |
| |
| /* Operation ****************************************************************/ |
| |
| /* Check if current TX description is busy */ |
| |
| #define TX_IS_BUSY(_priv) \ |
| (((_priv)->txcur->ctrl & EMAC_TXDMA_OWN) != 0) |
| |
| /* Get EMAC private data from net_driver_s */ |
| |
| #define NET2PRIV(_dev) ((struct esp32_emac_s *)(_dev)->d_private) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| struct esp32_emac_s |
| { |
| /* This holds the information visible to the NuttX network */ |
| |
| struct net_driver_s dev; /* Interface understood by the network */ |
| |
| uint8_t ifup : 1; /* true:ifup false:ifdown */ |
| uint8_t mbps100 : 1; /* 100MBps operation (vs 10 MBps) */ |
| uint8_t fduplex : 1; /* Full (vs. half) duplex */ |
| |
| struct wdog_s txtimeout; /* TX timeout timer */ |
| |
| struct work_s txwork; /* For deferring TX work to the work queue */ |
| struct work_s rxwork; /* For deferring RX work to the work queue */ |
| struct work_s timeoutwork; /* For TX timeout work to the work queue */ |
| struct work_s pollwork; /* For deferring poll work to the work queue */ |
| |
| uint8_t cpu; /* CPU ID */ |
| int cpuint; /* CPU interrupt assigned to EMAC */ |
| |
| sq_queue_t freeb; /* The free buffer list */ |
| |
| /* Hardware RX description allocations */ |
| |
| struct emac_rxdesc_s rxdesc[EMAC_RX_BUF_NUM]; |
| |
| /* Hardware TX description allocations */ |
| |
| struct emac_txdesc_s txdesc[EMAC_TX_BUF_NUM]; |
| |
| /* Current used RX description node */ |
| |
| struct emac_rxdesc_s *rxcur; |
| |
| /* Current used TX description node */ |
| |
| struct emac_txdesc_s *txcur; |
| |
| /* RX and TX buffer allocations */ |
| |
| uint8_t alloc[EMAC_BUF_NUM * EMAC_BUF_LEN]; |
| |
| /* Device specific lock. */ |
| |
| spinlock_t lock; |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| static struct esp32_emac_s s_esp32_emac; |
| static mutex_t g_lock = NXMUTEX_INITIALIZER; |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| static int emac_ifdown(struct net_driver_s *dev); |
| static int emac_ifup(struct net_driver_s *dev); |
| static void emac_dopoll(struct esp32_emac_s *priv); |
| static void emac_txtimeout_expiry(wdparm_t arg); |
| |
| /**************************************************************************** |
| * External Function Prototypes |
| ****************************************************************************/ |
| |
| extern uint8_t esp_crc8(const uint8_t *p, uint32_t len); |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: emac_set_reg |
| * |
| * Description: |
| * Set the contents of the EMAC register at offset |
| * |
| * Input Parameters: |
| * offset - Offset to the register of interest |
| * value - Value to be written |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void emac_set_reg(int offset, uint32_t value) |
| { |
| putreg32(value, EMAC_REG_BASE + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: emac_get_reg |
| * |
| * Description: |
| * Get the contents of the EMAC register at offset |
| * |
| * Input Parameters: |
| * offset - Offset to the register of interest |
| * |
| * Returned Value: |
| * The contents of the register |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t emac_get_reg(int offset) |
| { |
| return getreg32(EMAC_REG_BASE + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: emac_set_regbits |
| * |
| * Description: |
| * Set the bits of the EMAC register at offset |
| * |
| * Input Parameters: |
| * offset - Offset to the register of interest |
| * bits - Bits to be set |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void emac_set_regbits(int offset, uint32_t bits) |
| { |
| uint32_t tmp = getreg32(EMAC_REG_BASE + offset); |
| |
| putreg32(tmp | bits, EMAC_REG_BASE + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: emac_reset_regbits |
| * |
| * Description: |
| * Clear the bits of the EMAC register at offset |
| * |
| * Input Parameters: |
| * offset - Offset to the register of interest |
| * bits - Bits to be cleared |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void emac_reset_regbits(int offset, uint32_t bits) |
| { |
| uint32_t tmp = getreg32(EMAC_REG_BASE + offset); |
| |
| putreg32(tmp & (~bits), EMAC_REG_BASE + offset); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_init_buffer |
| * |
| * Description: |
| * Initialize the free buffer list |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called during early driver initialization before Ethernet interrupts |
| * are enabled |
| * |
| ****************************************************************************/ |
| |
| static void emac_init_buffer(struct esp32_emac_s *priv) |
| { |
| uint8_t *buffer = priv->alloc; |
| 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; i < EMAC_BUF_NUM; i++) |
| { |
| sq_addlast((sq_entry_t *)buffer, &priv->freeb); |
| buffer += EMAC_BUF_LEN; |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: emac_alloc_buffer |
| * |
| * Description: |
| * Allocate one buffer from the free buffer list |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * Pointer to the allocated buffer on success; NULL on failure |
| * |
| * Assumptions: |
| * Because we free buffer in interrupt, so add enter/exit critical here |
| * |
| ****************************************************************************/ |
| |
| static inline uint8_t *emac_alloc_buffer(struct esp32_emac_s *priv) |
| { |
| uint8_t *p; |
| irqstate_t flags; |
| |
| /* Allocate a buffer by returning the head of the free buffer list */ |
| |
| flags = spin_lock_irqsave(&priv->lock); |
| |
| p = (uint8_t *)sq_remfirst(&priv->freeb); |
| |
| spin_unlock_irqrestore(&priv->lock, flags); |
| |
| return p; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_free_buffer |
| * |
| * Description: |
| * Return a buffer to the free buffer list |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * buffer - A pointer to the buffer to be freed |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * This function is called in interrupt, so that this operation can |
| * speed up freeing the buffer which has been transmitted |
| * |
| ****************************************************************************/ |
| |
| static inline void emac_free_buffer(struct esp32_emac_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: emac_read_mac |
| * |
| * Description: |
| * Read MAC address from eFuse memory |
| * |
| * Input Parameters: |
| * mac - MAC address read buffer pointer |
| * |
| * Returned Value: |
| * 0 is returned on success or a negated errno value is returned |
| * |
| ****************************************************************************/ |
| |
| static int emac_read_mac(uint8_t *mac) |
| { |
| uint32_t regval[2]; |
| uint8_t *data = (uint8_t *)regval; |
| uint8_t crc; |
| int i; |
| |
| /* The MAC address in register is from high byte to low byte */ |
| |
| regval[0] = getreg32(MAC_ADDR0_REG); |
| regval[1] = getreg32(MAC_ADDR1_REG); |
| |
| crc = data[6]; |
| for (i = 0; i < 6; i++) |
| { |
| mac[i] = data[5 - i]; |
| } |
| |
| if (crc != esp_crc8(mac, 6)) |
| { |
| nerr("ERROR: Failed to check MAC address CRC\n"); |
| |
| return -EINVAL; |
| } |
| |
| #ifdef CONFIG_ESP_MAC_ADDR_UNIVERSE_ETH |
| mac[5] += 3; |
| #else |
| mac[5] += 1; |
| uint8_t tmp = mac[0]; |
| for (i = 0; i < 64; i++) |
| { |
| mac[0] = tmp | 0x02; |
| mac[0] ^= i << 2; |
| |
| if (mac[0] != tmp) |
| { |
| break; |
| } |
| } |
| |
| if (i >= 64) |
| { |
| wlerr("Failed to generate ethernet MAC\n"); |
| return -1; |
| } |
| #endif |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_init_gpio |
| * |
| * Description: |
| * Initialize ESP32 ethernet interface GPIO pin |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * NOne |
| * |
| ****************************************************************************/ |
| |
| static void emac_init_gpio(void) |
| { |
| esp32_configgpio(EMAC_TXEN_PIN, OUTPUT_FUNCTION_6); |
| esp32_configgpio(EMAC_TXDO_PIN, OUTPUT_FUNCTION_6); |
| esp32_configgpio(EMAC_TXD1_PIN, OUTPUT_FUNCTION_6); |
| |
| esp32_configgpio(EMAC_RXDO_PIN, INPUT_FUNCTION_6); |
| esp32_configgpio(EMAC_RXD1_PIN, INPUT_FUNCTION_6); |
| esp32_configgpio(EMAC_RXDV_PIN, INPUT_FUNCTION_6); |
| |
| esp32_configgpio(EMAC_ICLK_PIN, INPUT_FUNCTION_6); |
| |
| esp32_configgpio(EMAC_MDC_PIN, OUTPUT | FUNCTION_3); |
| esp32_gpio_matrix_out(EMAC_MDC_PIN, EMAC_MDC_O_IDX, 0, 0); |
| |
| esp32_configgpio(EMAC_MDIO_PIN, OUTPUT | INPUT | FUNCTION_3); |
| esp32_gpio_matrix_out(EMAC_MDIO_PIN, EMAC_MDO_O_IDX, 0, 0); |
| esp32_gpio_matrix_in(EMAC_MDIO_PIN, EMAC_MDI_I_IDX, 0); |
| |
| #ifdef CONFIG_ESP32_ETH_ENABLE_PHY_RSTPIN |
| esp32_configgpio(EMAC_PHYRST_PIN, OUTPUT | PULLUP); |
| #endif |
| } |
| |
| /**************************************************************************** |
| * Name: emac_config |
| * |
| * Description: |
| * Configure ESP32 ethernet |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EINVAL is returned if MAC address is invalid |
| * -ETIMEDOUT is returned if reset ESP32 MAC timeout |
| * |
| ****************************************************************************/ |
| |
| static int emac_config(void) |
| { |
| int i; |
| uint32_t regval; |
| uint8_t macaddr[6]; |
| |
| #ifdef CONFIG_ESP32_ETH_ENABLE_PHY_RSTPIN |
| |
| /* Hardware reset PHY chip */ |
| |
| esp32_gpiowrite(EMAC_PHYRST_PIN, false); |
| up_udelay(50); |
| esp32_gpiowrite(EMAC_PHYRST_PIN, true); |
| #endif |
| |
| /* Open hardware clock */ |
| |
| modifyreg32(DPORT_WIFI_CLK_EN_REG, 0, DPORT_EMAC_CLK_EN); |
| modifyreg32(DPORT_WIFI_RST_EN_REG, DPORT_EMAC_RST_EN, 0); |
| |
| /* Configure interface to be RMII */ |
| |
| emac_set_regbits(EMAC_PIR_OFFSET, EMAC_PIS_RMII); |
| |
| /* Use external XTAL clock for RMII */ |
| |
| emac_set_regbits(EMAC_EOCCR_OFFSET, EMAC_OSEC_E); |
| emac_set_regbits(EMAC_ECCR_OFFSET, EMAC_EXC_E); |
| |
| /* Configure SMI clock */ |
| |
| emac_set_regbits(EMAC_MAR_OFFSET, (EMAC_SMI_CLK) << EMAC_SMICS_S); |
| |
| /* Reset EMAC */ |
| |
| emac_set_regbits(EMAC_DMA_BMR_OFFSET, EMAC_SR_E); |
| |
| for (i = 0; i < EMAC_RESET_TO; i++) |
| { |
| if (!(emac_get_reg(EMAC_DMA_BMR_OFFSET) & EMAC_SR_E)) |
| { |
| break; |
| } |
| |
| up_udelay(10); |
| } |
| |
| if (i >= EMAC_RESET_TO) |
| { |
| nerr("ERROR: Failed to reset EMAC\n"); |
| |
| return -ETIMEDOUT; |
| } |
| |
| /* Enable transmission options: |
| * |
| * - 100M |
| * - Full duplex |
| */ |
| |
| regval = EMAC_FD_E | EMAC_100M_E | EMAC_SS_E | EMAC_RIPCOFFLOAD_E; |
| emac_set_reg(EMAC_CR_OFFSET, regval); |
| |
| /* Pass all multicast frame */ |
| |
| emac_set_reg(EMAC_FFR_OFFSET, EMAC_PMF_E); |
| |
| /* Enable flow control options: |
| * |
| * - PT-28 Time slot |
| * - RX flow control |
| * - TX flow control |
| * - Pause frame |
| */ |
| |
| regval = EMAC_RXFC_E | EMAC_TXFC_E | EMAC_FCBBA_E | |
| (EMAC_PLT_TYPE << EMAC_PFPT_S) | |
| (EMAC_PAUSE_TIME << EMAC_CFPT_S); |
| emac_set_reg(EMAC_FCR_OFFSET, regval); |
| |
| /* Enable DMA options: |
| * |
| * - Drop error frame |
| * - Send frame when filled into FiFO |
| * - Automatically Send next frame |
| */ |
| |
| regval = EMAC_FSF_E | EMAC_FTF_E | EMAC_OSF_E; |
| emac_set_reg(EMAC_DMA_OMR_OFFSET, regval); |
| |
| /* Enable DMA bus options: |
| * |
| * - Mixed burst mode |
| * - Address align beast |
| * - Separate PBL |
| * - Long DMA description |
| */ |
| |
| regval = EMAC_MB_E | EMAC_AAB_E | EMAC_SPBL_E | EMAC_ADS_E | |
| (EMAC_DMARXPBL_NUM << EMAC_RXDMA_PBL_S) | |
| (EMAC_DMATXPBL_NUM << EMAC_PBL_S); |
| emac_set_reg(EMAC_DMA_BMR_OFFSET, regval); |
| |
| if (emac_read_mac(macaddr) || (macaddr[0] & 0x01)) |
| { |
| nerr("ERROR: Failed read MAC address\n"); |
| |
| return -EINVAL; |
| } |
| |
| /* Configure hardware MAC address */ |
| |
| regval = (macaddr[4] << 0) | (macaddr[5] << 8); |
| emac_set_reg(EMAC_MA0HR_OFFSET, regval); |
| |
| regval = (macaddr[0] << 0) | (macaddr[1] << 8) | |
| (macaddr[2] << 16) | (macaddr[3] << 24); |
| emac_set_reg(EMAC_MA0LR_OFFSET, regval); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_start |
| * |
| * Description: |
| * Enable ESP32 EMAC transmission |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * NOne |
| * |
| ****************************************************************************/ |
| |
| static void emac_start(void) |
| { |
| uint32_t val = UINT32_MAX; |
| |
| /* Clear and enable all interrupt */ |
| |
| emac_set_reg(EMAC_DMA_SR_OFFSET, val); |
| emac_set_reg(EMAC_DMA_IER_OFFSET, val); |
| |
| emac_set_regbits(EMAC_DMA_OMR_OFFSET, EMAC_SST_E | EMAC_SSR_E); |
| |
| emac_set_regbits(EMAC_CR_OFFSET, EMAC_TX_E | EMAC_RX_E); |
| } |
| |
| /**************************************************************************** |
| * Name: emac_init_dma |
| * |
| * Description: |
| * Initialize DMA of EMAC |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void emac_init_dma(struct esp32_emac_s *priv) |
| { |
| int i; |
| struct emac_rxdesc_s *rxdesc = priv->rxdesc; |
| struct emac_txdesc_s *txdesc = priv->txdesc; |
| |
| emac_init_buffer(priv); |
| |
| for (i = 0 ; i < EMAC_RX_BUF_NUM; i++) |
| { |
| rxdesc[i].status = EMAC_RXDMA_OWN; |
| rxdesc[i].ctrl = EMAC_BUF_LEN | EMAC_RXDMA_RCH; |
| |
| /* Allocate buffer to prepare for receiving packets */ |
| |
| rxdesc[i].pbuf = emac_alloc_buffer(priv); |
| DEBUGASSERT(rxdesc[i].pbuf); |
| rxdesc[i].next = &rxdesc[i + 1]; |
| } |
| |
| rxdesc[i - 1].next = rxdesc; |
| priv->rxcur = &rxdesc[0]; |
| |
| for (i = 0 ; i < EMAC_TX_BUF_NUM; i++) |
| { |
| txdesc[i].pbuf = NULL; |
| txdesc[i].next = &txdesc[i + 1]; |
| } |
| |
| txdesc[i - 1].next = txdesc; |
| priv->txcur = &txdesc[0]; |
| |
| emac_set_reg(EMAC_DMA_RDBR_OFFSET, (uint32_t)rxdesc); |
| emac_set_reg(EMAC_DMA_TDBR_OFFSET, (uint32_t)txdesc); |
| } |
| |
| /**************************************************************************** |
| * Name: emac_deinit_dma |
| * |
| * Description: |
| * Deinitialize DMA of EMAC by force to free RX & TX buffer |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void emac_deinit_dma(struct esp32_emac_s *priv) |
| { |
| int i; |
| struct emac_rxdesc_s *rxdesc = priv->rxdesc; |
| struct emac_txdesc_s *txdesc = priv->txdesc; |
| |
| emac_init_buffer(priv); |
| |
| for (i = 0 ; i < EMAC_RX_BUF_NUM; i++) |
| { |
| if (rxdesc[i].pbuf) |
| { |
| emac_free_buffer(priv, rxdesc[i].pbuf); |
| rxdesc[i].pbuf = NULL; |
| } |
| } |
| |
| for (i = 0 ; i < EMAC_TX_BUF_NUM; i++) |
| { |
| if (txdesc[i].pbuf) |
| { |
| emac_free_buffer(priv, txdesc[i].pbuf); |
| txdesc[i].pbuf = NULL; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: emac_transmit |
| * |
| * Description: |
| * Start hardware transmission. Called either from the txdone interrupt |
| * handling or from watchdog based polling. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if no TX descrption is valid. |
| * |
| ****************************************************************************/ |
| |
| static int emac_transmit(struct esp32_emac_s *priv) |
| { |
| int ret; |
| struct emac_txdesc_s *txcur = priv->txcur; |
| |
| if (txcur->ctrl & EMAC_TXDMA_OWN) |
| { |
| return -EBUSY; |
| } |
| |
| if (txcur->pbuf) |
| { |
| emac_free_buffer(priv, txcur->pbuf); |
| } |
| |
| txcur->pbuf = priv->dev.d_buf; |
| txcur->ctrl = EMAC_TXDMA_OWN | EMAC_TXDMA_FS | EMAC_TXDMA_LS | |
| EMAC_TXDMA_CI | EMAC_TXDMA_TTSS | EMAC_TXDMA_TCH; |
| txcur->ext_ctrl = priv->dev.d_len; |
| |
| priv->txcur = txcur->next; |
| |
| emac_set_reg(EMAC_DMA_STR_OFFSET, 0); |
| |
| ninfo("d_buf=%p d_len=%d\n", priv->dev.d_buf, priv->dev.d_len); |
| |
| priv->dev.d_buf = NULL; |
| priv->dev.d_len = 0; |
| |
| /* Setup the TX timeout watchdog (perhaps restarting the timer) */ |
| |
| ret = wd_start(&priv->txtimeout, EMAC_TX_TO, |
| emac_txtimeout_expiry, (wdparm_t)priv); |
| if (ret) |
| { |
| nerr("ERROR: Failed to start TX timeout timer"); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_recvframe |
| * |
| * Description: |
| * An interrupt was received indicating the availability of a new RX packet |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static int emac_recvframe(struct esp32_emac_s *priv) |
| { |
| uint32_t len; |
| struct emac_rxdesc_s *rxcur = priv->rxcur; |
| |
| if (rxcur->status & EMAC_RXDMA_OWN) |
| { |
| return -EBUSY; |
| } |
| |
| if (!rxcur->pbuf) |
| { |
| return -EINVAL; |
| } |
| |
| len = (rxcur->status >> EMAC_RXDMA_FL_S) & EMAC_RXDMA_FL_V; |
| priv->dev.d_buf = (uint8_t *)rxcur->pbuf; |
| priv->dev.d_len = len - ETH_CRC_LEN; |
| |
| rxcur->pbuf = emac_alloc_buffer(priv); |
| DEBUGASSERT(rxcur->pbuf); |
| rxcur->status = EMAC_RXDMA_OWN; |
| rxcur->ctrl = EMAC_BUF_LEN | EMAC_RXDMA_RCH; |
| |
| priv->rxcur = rxcur->next; |
| |
| emac_set_reg(EMAC_DMA_SRR_OFFSET, 0); |
| |
| ninfo("RX bytes %d\n", priv->dev.d_len); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_read_phy |
| * |
| * Description: |
| * Read PHY chip register value |
| * |
| * Input Parameters: |
| * dev_addr - PHY chip address |
| * reg_addr - register address |
| * pdata - buffer pointer of register value |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if device is busy |
| * -ETIMEDOUT is returned if read timeout |
| * |
| ****************************************************************************/ |
| |
| static int emac_read_phy(uint16_t dev_addr, |
| uint16_t reg_addr, |
| uint16_t *pdata) |
| { |
| uint16_t val; |
| int i; |
| |
| val = emac_get_reg(EMAC_MAR_OFFSET); |
| if (val & EMAC_PIB) |
| { |
| return -EBUSY; |
| } |
| |
| val = ((dev_addr & EMAC_PCA_V) << EMAC_PCA_S) | |
| ((reg_addr & EMAC_PCRA_V) << EMAC_PCRA_S) | |
| EMAC_PIB; |
| |
| emac_set_reg(EMAC_MAR_OFFSET, val); |
| |
| for (i = 0; i < EMAC_READPHY_TO; i++) |
| { |
| up_udelay(100); |
| |
| val = emac_get_reg(EMAC_MAR_OFFSET); |
| if (!(val & EMAC_PIB)) |
| { |
| break; |
| } |
| } |
| |
| if (i >= EMAC_READPHY_TO) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| *pdata = emac_get_reg(EMAC_MDR_OFFSET); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_write_phy |
| * |
| * Description: |
| * Write value to PHY chip register |
| * |
| * Input Parameters: |
| * dev_addr - PHY chip address |
| * reg_addr - register address |
| * data - register value |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if device is busy |
| * -ETIMEDOUT is returned if write timeout |
| * |
| ****************************************************************************/ |
| |
| static int emac_write_phy(uint16_t dev_addr, |
| uint16_t reg_addr, |
| uint16_t data) |
| { |
| uint16_t val; |
| int i; |
| |
| val = emac_get_reg(EMAC_MAR_OFFSET); |
| if (val & EMAC_PIB) |
| { |
| return -EBUSY; |
| } |
| |
| emac_set_reg(EMAC_MDR_OFFSET, data); |
| |
| val = ((dev_addr & EMAC_PCA_V) << EMAC_PCA_S) | |
| ((reg_addr & EMAC_PCRA_V) << EMAC_PCRA_S) | |
| EMAC_PIB; |
| |
| emac_set_reg(EMAC_MAR_OFFSET, val); |
| |
| for (i = 0; i < EMAC_WRITEPHY_TO; i++) |
| { |
| up_udelay(100); |
| |
| val = emac_get_reg(EMAC_MAR_OFFSET); |
| if (!(val & EMAC_PIB)) |
| { |
| break; |
| } |
| } |
| |
| if (i >= EMAC_WRITEPHY_TO) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_wait_linkup |
| * |
| * Description: |
| * Wait for ethernet link up or timeout |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if device is busy |
| * -ETIMEDOUT is returned if write timeout |
| * |
| ****************************************************************************/ |
| |
| static int emac_wait_linkup(struct esp32_emac_s *priv) |
| { |
| int ret; |
| int i; |
| uint16_t val; |
| |
| for (i = 0; i < EMAC_WAITLINK_TO; i++) |
| { |
| up_udelay(10); |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_MSR, &val); |
| if (ret != 0) |
| { |
| priv->ifup = false; |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_MSR, ret); |
| return ret; |
| } |
| |
| if (val & MII_MSR_LINKSTATUS) |
| { |
| break; |
| } |
| } |
| |
| ninfo("PHY register 0x%x is: 0x%04x\n", MII_MSR, val); |
| |
| if (i >= EMAC_WAITLINK_TO) |
| { |
| nerr("ERROR: Timeout to wait for PHY LINK UP"); |
| priv->ifup = false; |
| return -ETIMEDOUT; |
| } |
| else |
| { |
| priv->ifup = true; |
| } |
| |
| if (val & MII_MSR_100BASETXFULL) |
| { |
| priv->mbps100 = true; |
| priv->fduplex = true; |
| } |
| else if (val & MII_MSR_100BASETXHALF) |
| { |
| priv->mbps100 = true; |
| priv->fduplex = false; |
| } |
| else if (val & MII_MSR_10BASETXFULL) |
| { |
| priv->mbps100 = false; |
| priv->fduplex = true; |
| } |
| else if (val & MII_MSR_10BASETXHALF) |
| { |
| priv->mbps100 = false; |
| priv->fduplex = false; |
| } |
| else if (val & MII_MSR_100BASET2HALF) |
| { |
| priv->mbps100 = true; |
| priv->fduplex = false; |
| } |
| else if (val & MII_MSR_100BASET2FULL) |
| { |
| priv->mbps100 = true; |
| priv->fduplex = true; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Name: emac_init_phy |
| * |
| * Description: |
| * Brings up and initializes PHY chip |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if phy is busy |
| * -ETIMEDOUT is returned if read/write phy register timeout |
| * -EPERM is returned if operation fails |
| * |
| ****************************************************************************/ |
| |
| static int emac_init_phy(struct esp32_emac_s *priv) |
| { |
| int ret; |
| int i; |
| uint16_t val; |
| |
| /* power on PHY chip */ |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_MCR, &val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_MCR, ret); |
| return ret; |
| } |
| |
| ninfo("PHY register 0x%x is: 0x%04x\n", MII_MCR, val); |
| |
| val &= ~(MII_MCR_PDOWN); |
| |
| ret = emac_write_phy(EMAC_PHY_ADDR, MII_MCR, val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to write PHY Register 0x%x to be 0x%x: %d\n", |
| MII_MCR, val, ret); |
| return ret; |
| } |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_MCR, &val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_MCR, ret); |
| return ret; |
| } |
| |
| if (val & MII_MCR_PDOWN) |
| { |
| nerr("ERROR: Failed to power on PHY\n"); |
| return -EPERM; |
| } |
| |
| val |= MII_MCR_RESET; |
| |
| ret = emac_write_phy(EMAC_PHY_ADDR, MII_MCR, val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to write PHY Register 0x%x to be 0x%x: %d\n", |
| MII_MCR, val, ret); |
| return ret; |
| } |
| |
| for (i = 0; i < EMAC_RSTPHY_TO; i++) |
| { |
| up_udelay(100); |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_MCR, &val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_MCR, ret); |
| return ret; |
| } |
| |
| if (!(val & MII_MCR_RESET)) |
| { |
| break; |
| } |
| } |
| |
| if (i >= EMAC_RSTPHY_TO) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_PHYID1, &val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_PHYID1, ret); |
| return ret; |
| } |
| |
| ninfo("PHY register 0x%x is: 0x%04x\n", MII_PHYID1, val); |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_PHYID2, &val); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to read PHY Register 0x%x: %d\n", |
| MII_PHYID2, ret); |
| return ret; |
| } |
| |
| ninfo("PHY register 0x%x is: 0x%04x\n", MII_PHYID2, val); |
| |
| ret = emac_wait_linkup(priv); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to wait LINK UP error=%d\n", ret); |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: phy_enable_interrupt |
| * |
| * Description: |
| * Enable link up/down PHY interrupts. |
| * |
| * Input Parameters: |
| * None. |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EBUSY is returned if IP101 is busy. |
| * -ETIMEDOUT is returned if read/write IP101 register timeout. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) |
| static int phy_enable_interrupt(void) |
| { |
| uint16_t phyval; |
| int ret; |
| |
| ret = emac_read_phy(EMAC_PHY_ADDR, MII_INT_REG, ®val); |
| if (ret == OK) |
| { |
| /* Enable link up/down interrupts */ |
| |
| ret = emac_write_phy(EMAC_PHY_ADDR, MII_INT_REG, |
| (regval & ~MII_INT_CLREN) | MII_INT_SETEN); |
| } |
| |
| return ret; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: emac_txtimeout_work |
| * |
| * Description: |
| * Perform TX timeout related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() as called. |
| * |
| * Returned Value: |
| * 0 on success |
| * |
| * Assumptions: |
| * Ethernet interrupts are disabled |
| * |
| ****************************************************************************/ |
| |
| static void emac_txtimeout_work(void *arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_s *)arg; |
| |
| /* Reset the hardware. Just take the interface down, then back up again. */ |
| |
| net_lock(); |
| emac_ifdown(&priv->dev); |
| emac_ifup(&priv->dev); |
| |
| /* Then poll for new XMIT data */ |
| |
| emac_dopoll(priv); |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_txtimeout_expiry |
| * |
| * Description: |
| * Our TX watchdog timed out. Called from the timer interrupt handler. |
| * The last TX never completed. Reset the hardware and start again. |
| * |
| * Input Parameters: |
| * arg - The argument |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static void emac_txtimeout_expiry(wdparm_t arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_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 emac_ifup() is called. |
| */ |
| |
| up_disable_irq(ESP32_IRQ_EMAC); |
| |
| /* Schedule to perform the TX timeout processing on the worker thread, |
| * perhaps canceling any pending IRQ processing. |
| */ |
| |
| work_queue(ETHWORK, &priv->timeoutwork, emac_txtimeout_work, priv, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_rx_interrupt_work |
| * |
| * Description: |
| * Perform interrupt related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() was called. |
| * |
| * Returned Value: |
| * 0 on success |
| * |
| * Assumptions: |
| * Ethernet interrupts are disabled |
| * |
| ****************************************************************************/ |
| |
| static void emac_rx_interrupt_work(void *arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_s *)arg; |
| struct net_driver_s *dev = &priv->dev; |
| |
| net_lock(); |
| |
| /* Loop while while emac_recvframe() successfully retrieves valid |
| * Ethernet frames. |
| */ |
| |
| while (emac_recvframe(priv) == 0) |
| { |
| struct eth_hdr_s *eth_hdr = (struct eth_hdr_s *)dev->d_buf; |
| |
| #ifdef CONFIG_NET_PKT |
| /* When packet sockets are enabled, feed the frame into the packet tap |
| */ |
| |
| pkt_input(&priv->dev); |
| #endif |
| |
| /* We only accept IP packets of the configured type and ARP packets |
| */ |
| |
| #ifdef CONFIG_NET_IPv4 |
| if (eth_hdr->type == HTONS(ETHTYPE_IP)) |
| { |
| ninfo("IPv4 frame\n"); |
| |
| /* Receive an IPv4 packet from the network device */ |
| |
| ipv4_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, the field d_len will set to a value > 0 |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* And send the packet */ |
| |
| emac_transmit(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_IPv6 |
| if (eth_hdr->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) |
| { |
| /* And send the packet */ |
| |
| emac_transmit(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_ARP |
| if (eth_hdr->type == HTONS(ETHTYPE_ARP)) |
| { |
| ninfo("ARP frame\n"); |
| |
| /* Handle ARP packet */ |
| |
| arp_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, the field d_len will set to a value > 0 |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| emac_transmit(priv); |
| } |
| } |
| else |
| #endif |
| { |
| nerr("ERROR: Dropped, Unknown type: %04x\n", eth_hdr->type); |
| } |
| |
| if (dev->d_buf) |
| { |
| emac_free_buffer(priv, dev->d_buf); |
| |
| dev->d_buf = NULL; |
| dev->d_len = 0; |
| } |
| } |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_tx_interrupt_work |
| * |
| * Description: |
| * Perform interrupt related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() was called. |
| * |
| * Returned Value: |
| * 0 on success |
| * |
| * Assumptions: |
| * Ethernet interrupts are disabled |
| * |
| ****************************************************************************/ |
| |
| static void emac_tx_interrupt_work(void *arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_s *)arg; |
| |
| net_lock(); |
| |
| wd_cancel(&priv->txtimeout); |
| |
| emac_dopoll(priv); |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_interrupt |
| * |
| * Description: |
| * Hardware interrupt handler |
| * |
| * Input Parameters: |
| * irq - Number of the IRQ that generated the interrupt |
| * context - Interrupt register state save info (architecture-specific) |
| * |
| * Returned Value: |
| * 0 on success |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int emac_interrupt(int irq, void *context, void *arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_s *)arg; |
| uint32_t value = emac_get_reg(EMAC_DMA_SR_OFFSET); |
| |
| emac_set_reg(EMAC_DMA_SR_OFFSET, 0xffffffff); |
| |
| /* If the netcard is disabled then exit directly */ |
| |
| if (!priv->ifup) |
| { |
| return 0; |
| } |
| |
| if (value & EMAC_RI) |
| { |
| work_queue(EMACWORK, &priv->rxwork, emac_rx_interrupt_work, priv, 0); |
| } |
| |
| if (value & EMAC_TI) |
| { |
| work_queue(EMACWORK, &priv->txwork, emac_tx_interrupt_work, priv, 0); |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_txpoll |
| * |
| * Description: |
| * The transmitter is available, check if the network has any outgoing |
| * packets ready to send. This is a callback from devif_poll(). |
| * devif_poll() may be called: |
| * |
| * 1. When the preceding TX packet send is complete, |
| * 2. When the preceding TX packet send timesout and the interface is reset |
| * 3. During normal TX polling |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * 0 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 emac_txpoll(struct net_driver_s *dev) |
| { |
| struct esp32_emac_s *priv = NET2PRIV(dev); |
| |
| DEBUGASSERT(priv->dev.d_buf != NULL); |
| |
| /* Send the packet */ |
| |
| emac_transmit(priv); |
| DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); |
| |
| /* Check if the current 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. |
| */ |
| |
| if (TX_IS_BUSY(priv)) |
| { |
| /* We have to terminate the poll if we have no more descriptors |
| * available for another transfer. |
| */ |
| |
| return -EBUSY; |
| } |
| |
| dev->d_buf = (uint8_t *)emac_alloc_buffer(priv); |
| if (dev->d_buf == NULL) |
| { |
| return -ENOMEM; |
| } |
| |
| dev->d_len = EMAC_BUF_LEN; |
| |
| /* If zero is returned, the polling will continue until all connections |
| * have been examined. |
| */ |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_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 (esp32_txdone), |
| * 2. When new TX data is available (emac_txavail_work), and |
| * 3. After a TX timeout to restart the sending process |
| * (esp32_txtimeout_process). |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Global interrupts are disabled by interrupt handling logic. |
| * |
| ****************************************************************************/ |
| |
| static void emac_dopoll(struct esp32_emac_s *priv) |
| { |
| struct net_driver_s *dev = &priv->dev; |
| |
| if (!TX_IS_BUSY(priv)) |
| { |
| DEBUGASSERT(dev->d_len == 0 && dev->d_buf == NULL); |
| |
| dev->d_buf = (uint8_t *)emac_alloc_buffer(priv); |
| if (!dev->d_buf) |
| { |
| /* never reach */ |
| |
| return ; |
| } |
| |
| dev->d_len = EMAC_BUF_LEN; |
| |
| devif_poll(dev, emac_txpoll); |
| |
| if (dev->d_buf) |
| { |
| emac_free_buffer(priv, dev->d_buf); |
| |
| dev->d_buf = NULL; |
| dev->d_len = 0; |
| } |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: emac_txavail_work |
| * |
| * Description: |
| * Perform an out-of-cycle poll on the worker thread. |
| * |
| * Input Parameters: |
| * arg - Reference to the NuttX driver state structure (cast to void*) |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called on the higher priority worker thread. |
| * |
| ****************************************************************************/ |
| |
| static void emac_txavail_work(void *arg) |
| { |
| struct esp32_emac_s *priv = (struct esp32_emac_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 */ |
| |
| emac_dopoll(priv); |
| } |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: emac_ifup |
| * |
| * Description: |
| * NuttX Callback: Bring up the Ethernet interface when an IP address is |
| * provided |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -EINVAL is returned if MAC address is invalid. |
| * -ETIMEDOUT is returned if reset ESP32 MAC timeout. |
| * |
| ****************************************************************************/ |
| |
| static int emac_ifup(struct net_driver_s *dev) |
| { |
| int ret; |
| irqstate_t flags; |
| struct esp32_emac_s *priv = NET2PRIV(dev); |
| |
| flags = enter_critical_section(); |
| |
| /* initialize ESP32 ethernet GPIO */ |
| |
| emac_init_gpio(); |
| |
| /* configure ESP32 ethernet MAC */ |
| |
| ret = emac_config(); |
| if (ret) |
| { |
| leave_critical_section(flags); |
| nerr("ERROR: Failed to configure ESP32 MAC\n"); |
| |
| return ret; |
| } |
| |
| /* initialize IP101 phy chip */ |
| |
| ret = emac_init_phy(priv); |
| if (ret) |
| { |
| leave_critical_section(flags); |
| nerr("ERROR: Failed to initialize IP101 phy chip\n"); |
| |
| return ret; |
| } |
| |
| /* initialize ESP32 ethernet MAC DMA */ |
| |
| emac_init_dma(priv); |
| |
| /* start ESP32 ethernet MAC */ |
| |
| emac_start(); |
| |
| /* Enable the Ethernet interrupt */ |
| |
| up_enable_irq(ESP32_IRQ_EMAC); |
| |
| leave_critical_section(flags); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_ifdown |
| * |
| * Description: |
| * NuttX Callback: Stop the interface. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * Returns zero on success; a negated errno value is returned on any |
| * failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int emac_ifdown(struct net_driver_s *dev) |
| { |
| struct esp32_emac_s *priv = NET2PRIV(dev); |
| irqstate_t flags; |
| |
| ninfo("Taking the network down\n"); |
| |
| /* Disable the Ethernet interrupt */ |
| |
| flags = enter_critical_section(); |
| |
| /* Disable TX and RX */ |
| |
| emac_reset_regbits(EMAC_CR_OFFSET, EMAC_TX_E | EMAC_RX_E); |
| |
| /* Disable the Ethernet interrupt */ |
| |
| up_disable_irq(ESP32_IRQ_EMAC); |
| |
| /* Cancel the TX timeout timers */ |
| |
| wd_cancel(&priv->txtimeout); |
| |
| /* Reset ethernet MAC and disable clock */ |
| |
| modifyreg32(DPORT_WIFI_RST_EN_REG, 0, DPORT_EMAC_RST_EN); |
| modifyreg32(DPORT_WIFI_CLK_EN_REG, DPORT_EMAC_CLK_EN, 0); |
| |
| /* Free DMA resource */ |
| |
| emac_deinit_dma(priv); |
| |
| /* Mark the device "down" */ |
| |
| priv->ifup = false; |
| |
| leave_critical_section(flags); |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_txavail |
| * |
| * Description: |
| * Driver callback invoked when new TX data is available. This is a |
| * stimulus perform an out-of-cycle poll and, thereby, reduce the TX |
| * latency. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Called in normal user mode |
| * |
| ****************************************************************************/ |
| |
| static int emac_txavail(struct net_driver_s *dev) |
| { |
| struct esp32_emac_s *priv = NET2PRIV(dev); |
| |
| /* 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(EMACWORK, &priv->pollwork, emac_txavail_work, priv, 0); |
| } |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: emac_addmac |
| * |
| * Description: |
| * NuttX Callback: Add the specified MAC address to the hardware multicast |
| * address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be added |
| * |
| * Returned Value: |
| * None |
| * |
| * Note: |
| * The default option can allow EMAC receive all multicast frame. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NET_MCASTGROUP) || defined(CONFIG_NET_ICMPv6) |
| static int emac_addmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| |
| return 0; |
| } |
| #endif /* CONFIG_NET_MCASTGROUP || CONFIG_NET_ICMPv6 */ |
| |
| /**************************************************************************** |
| * Function: emac_rmmac |
| * |
| * Description: |
| * NuttX Callback: Remove the specified MAC address from the hardware |
| * multicast address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be removed |
| * |
| * Returned Value: |
| * None |
| * |
| * Note: |
| * The default option can allow EMAC receive all multicast frame. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int emac_rmmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| |
| return 0; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: emac_ioctl |
| * |
| * Description: |
| * Executes the SIOCxMIIxxx command and responds using the request struct |
| * that must be provided as its 2nd parameter. |
| * |
| * When called with SIOCGMIIPHY it will get the PHY address for the device |
| * and write it to the req->phy_id field of the request struct. |
| * |
| * When called with SIOCGMIIREG it will read a register of the PHY that is |
| * specified using the req->reg_no struct field and then write its output |
| * to the req->val_out field. |
| * |
| * When called with SIOCSMIIREG it will write to a register of the PHY that |
| * is specified using the req->reg_no struct field and use req->val_in as |
| * its input. |
| * |
| * Input Parameters: |
| * dev - Ethernet device structure |
| * cmd - SIOCxMIIxxx command code |
| * arg - Request structure also used to return values |
| * |
| * Returned Value: |
| * 0 on success; Negated errno on failure. |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NETDEV_IOCTL |
| static int emac_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg) |
| { |
| int ret; |
| |
| /* Get exclusive access to the device structures */ |
| |
| ret = nxmutex_lock(&g_lock); |
| if (ret < 0) |
| { |
| return ret; |
| } |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_NETDEV_PHY_IOCTL |
| #ifdef CONFIG_ARCH_PHY_INTERRUPT |
| case SIOCMIINOTIFY: /* Set up for PHY event notifications */ |
| { |
| struct mii_ioctl_notify_s *req = |
| (struct mii_ioctl_notify_s *)((uintptr_t)arg); |
| |
| ret = phy_notify_subscribe(dev->d_ifname, req->pid, &req->event); |
| if (ret == 0) |
| { |
| /* Enable PHY link up/down interrupts */ |
| |
| ret = phy_enable_interrupt(); |
| } |
| } |
| 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 = EMAC_PHY_ADDR; |
| ret = 0; |
| } |
| break; |
| |
| case SIOCGMIIREG: /* Get register from MII PHY */ |
| { |
| struct mii_ioctl_data_s *req = |
| (struct mii_ioctl_data_s *)((uintptr_t)arg); |
| ret = emac_read_phy(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 = emac_write_phy(req->phy_id, req->reg_num, req->val_in); |
| } |
| break; |
| #endif /* CONFIG_NETDEV_PHY_IOCTL */ |
| |
| default: |
| ret = -ENOTTY; |
| break; |
| } |
| |
| nxmutex_unlock(&g_lock); |
| return ret; |
| } |
| #endif /* CONFIG_NETDEV_IOCTL */ |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: esp32_emac_init |
| * |
| * Description: |
| * Initialize ESP32 ethernet device driver. |
| * |
| * Input Parameters: |
| * None |
| * |
| * Returned Value: |
| * 0 is returned on success. Otherwise, a negated errno value is |
| * returned indicating the nature of the failure: |
| * |
| * -ENOMEM is returned if no memory resource. |
| * |
| ****************************************************************************/ |
| |
| int esp32_emac_init(void) |
| { |
| struct esp32_emac_s *priv = &s_esp32_emac; |
| int ret; |
| |
| memset(priv, 0, sizeof(struct esp32_emac_s)); |
| |
| priv->cpu = up_cpu_index(); |
| priv->cpuint = esp32_setup_irq(priv->cpu, ESP32_PERIPH_EMAC, |
| 1, ESP32_CPUINT_LEVEL); |
| if (priv->cpuint < 0) |
| { |
| nerr("ERROR: Failed alloc interrupt\n"); |
| |
| ret = -ENOMEM; |
| goto error; |
| } |
| |
| ret = irq_attach(ESP32_IRQ_EMAC, emac_interrupt, priv); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed attach interrupt\n"); |
| |
| ret = -ENOMEM; |
| goto errout_with_attachirq; |
| } |
| |
| /* Initialize the driver structure */ |
| |
| priv->dev.d_ifup = emac_ifup; /* I/F up (new IP address) callback */ |
| priv->dev.d_ifdown = emac_ifdown; /* I/F down callback */ |
| priv->dev.d_txavail = emac_txavail; /* New TX data callback */ |
| #ifdef CONFIG_NET_MCASTGROUP |
| priv->dev.d_addmac = emac_addmac; /* Add multicast MAC address */ |
| priv->dev.d_rmmac = emac_rmmac; /* Remove multicast MAC address */ |
| #endif |
| #ifdef CONFIG_NETDEV_IOCTL |
| priv->dev.d_ioctl = emac_ioctl; /* Support PHY ioctl() calls */ |
| #endif |
| |
| /* Used to recover private state from dev */ |
| |
| priv->dev.d_private = priv; |
| |
| emac_read_mac(priv->dev.d_mac.ether.ether_addr_octet); |
| |
| /* Register the device with the OS so that socket IOCTLs can be performed */ |
| |
| ret = netdev_register(&priv->dev, NET_LL_ETHERNET); |
| if (ret != 0) |
| { |
| nerr("ERROR: Failed to register net device\n"); |
| |
| goto errout_with_attachirq; |
| } |
| |
| return 0; |
| |
| errout_with_attachirq: |
| esp32_teardown_irq(priv->cpu, ESP32_PERIPH_EMAC, priv->cpuint); |
| |
| error: |
| return ret; |
| } |
| |
| /**************************************************************************** |
| * Function: xtensa_netinitialize |
| * |
| * Description: |
| * This is the "standard" network initialization logic called from the |
| * low-level initialization logic in up_initialize.c. |
| * |
| * Input Parameters: |
| * None. |
| * |
| * Returned Value: |
| * None. |
| * |
| ****************************************************************************/ |
| |
| #if !defined(CONFIG_NETDEV_LATEINIT) |
| void xtensa_netinitialize(void) |
| { |
| esp32_emac_init(); |
| } |
| #endif |
| |
| #endif /* CONFIG_ESP32_EMAC */ |