| /**************************************************************************** |
| * arch/arm/src/imxrt/imxrt_enet.c |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. The |
| * ASF licenses this file to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance with the |
| * License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT |
| * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the |
| * License for the specific language governing permissions and limitations |
| * under the License. |
| * |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Included Files |
| ****************************************************************************/ |
| |
| #include <nuttx/config.h> |
| |
| #include <inttypes.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| #include <time.h> |
| #include <string.h> |
| #include <assert.h> |
| #include <debug.h> |
| #include <errno.h> |
| #include <barriers.h> |
| #include <endian.h> |
| |
| #include <arpa/inet.h> |
| |
| #include <nuttx/wdog.h> |
| #include <nuttx/irq.h> |
| #include <nuttx/arch.h> |
| #include <nuttx/spinlock.h> |
| #include <nuttx/wqueue.h> |
| #include <nuttx/signal.h> |
| #include <nuttx/net/mii.h> |
| #include <nuttx/net/phy.h> |
| #include <nuttx/net/ip.h> |
| #include <nuttx/net/netdev.h> |
| |
| #ifdef CONFIG_NET_PKT |
| # include <nuttx/net/pkt.h> |
| #endif |
| |
| #include <arch/board/board.h> |
| |
| #include "arm_internal.h" |
| #include "chip.h" |
| #include "imxrt_config.h" |
| #include "hardware/imxrt_enet.h" |
| #include "hardware/imxrt_ccm.h" |
| #include "hardware/imxrt_pinmux.h" |
| #include "imxrt_periphclks.h" |
| #include "imxrt_gpio.h" |
| #include "imxrt_enet.h" |
| #ifdef CONFIG_ARCH_FAMILY_IMXRT117x |
| #include "hardware/rt117x/imxrt117x_ocotp.h" |
| #else |
| #include "hardware/imxrt_ocotp.h" |
| #endif |
| |
| #ifdef CONFIG_IMXRT_ENET |
| |
| /**************************************************************************** |
| * Pre-processor Definitions |
| ****************************************************************************/ |
| |
| /* 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. Always use the LP work queue if available. If not, |
| * then LPWORK will re-direct to the HP work queue. |
| * |
| * NOTE: However, the network should NEVER run on the high priority work |
| * queue! That queue is intended only to service short back end interrupt |
| * processing that never suspends. Suspending the high priority work queue |
| * may bring the system to its knees! |
| */ |
| |
| # define ETHWORK LPWORK |
| #endif |
| |
| /* CONFIG_IMXRT_ENET_NETHIFS determines the number of physical interfaces |
| * that will be supported. |
| * |
| * The current driver only supports one interface chosen at compile time. |
| * It may be either ETH1 or ETH2. In the future 2 interfaces may be |
| * supported. |
| */ |
| |
| #if CONFIG_IMXRT_ENET_NETHIFS != 1 |
| # error "CONFIG_IMXRT_ENET_NETHIFS must be one for now" |
| #endif |
| |
| #if defined(CONFIG_IMXRT_ENET1) && defined(CONFIG_IMXRT_ENET2) |
| # error "The current driver only supports one interface CONFIG_IMXRT_ENET{1|2}" |
| #endif |
| |
| #if defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) && \ |
| defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # error "Only one of CONFIG_IMXRT_PHY_PROVIDES_TXC, CONFIG_IMXRT_MAC_PROVIDES_TXC can be selected" |
| #endif |
| #if !defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) && \ |
| !defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # error "One of CONFIG_IMXRT_PHY_PROVIDES_TXC, CONFIG_IMXRT_MAC_PROVIDES_TXC must be selected" |
| #endif |
| |
| #ifdef CONFIG_ARCH_FAMILY_IMXRT117x |
| # if defined(CONFIG_IMXRT_ENET1) |
| # define IMXRT_ENET_IOMUXC_GPR IMXRT_IOMUXC_GPR_GPR4 |
| # define imxrt_clock_enet imxrt_clockall_enet |
| # define GPR_ENET_MASK (GPR_GPR4_ENET_TX_CLK_SEL | \ |
| GPR_GPR4_ENET_REF_CLK_DIR) |
| # define IMXRT_ENET_IRQ IMXRT_IRQ_ENET |
| # define IMXRT_ENETN_BASE IMXRT_ENET_BASE |
| # if defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR4_ENET_REF_CLK_DIR_OUT |
| # define GPR_ENET_CLK_SEL GPR_GPR4_ENET_TX_CLK_SEL_NS |
| # endif |
| # if defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR4_ENET_REF_CLK_DIR_IN |
| # define GPR_ENET_CLK_SEL GPR_GPR4_ENET_TX_CLK_SEL_PAD |
| # endif |
| # endif |
| # if defined(CONFIG_IMXRT_ENET2) |
| # define IMXRT_ENET_IOMUXC_GPR IMXRT_IOMUXC_GPR_GPR5 |
| # define imxrt_clock_enet imxrt_clockall_enet2 |
| # define GPR_ENET_MASK (GPR_GPR5_ENET1G_TX_CLK_SEL | \ |
| GPR_GPR5_ENET1G_REF_CLK_DIR) |
| # define IMXRT_ENET_IRQ IMXRT_IRQ_ENET2_1 |
| # define IMXRT_ENET_IRQ_2 IMXRT_IRQ_ENET2_2 |
| # define IMXRT_ENET_IRQ_3 IMXRT_IRQ_ENET2_3 |
| # define IMXRT_ENETN_BASE IMXRT_ENET_1G_BASE |
| # if defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR5_ENET1G_REF_CLK_DIR_OUT |
| # define GPR_ENET_CLK_SEL GPR_GPR5_ENET1G_TX_CLK_SEL_CLK |
| # endif |
| # if defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR5_ENET1G_REF_CLK_DIR_IN |
| # define GPR_ENET_CLK_SEL GPR_GPR5_ENET1G_TX_CLK_SEL_PAD |
| # endif |
| # endif |
| #else |
| # define IMXRT_ENET_IOMUXC_GPR IMXRT_IOMUXC_GPR_GPR1 |
| |
| # if defined(CONFIG_IMXRT_ENET1) |
| # define imxrt_clock_enet imxrt_clockall_enet |
| # define GPR_ENET_MASK (GPR_GPR1_ENET1_CLK_SEL | \ |
| GPR_GPR1_ENET1_TX_DIR_OUT) |
| # define IMXRT_ENET_IRQ IMXRT_IRQ_ENET |
| # define IMXRT_ENETN_BASE IMXRT_ENET_BASE |
| # if defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR1_ENET1_TX_DIR_OUT |
| # define GPR_ENET_CLK_SEL 0 |
| # endif |
| # if defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR1_ENET1_TX_DIR_IN |
| # define GPR_ENET_CLK_SEL GPR_GPR1_ENET1_CLK_SEL |
| # endif |
| # endif |
| |
| # if defined(CONFIG_IMXRT_ENET2) |
| # define imxrt_clock_enet imxrt_clockall_enet2 |
| # define GPR_ENET_MASK (GPR_GPR1_ENET2_CLK_SEL | \ |
| GPR_GPR1_ENET2_TX_DIR_OUT) |
| # define IMXRT_ENET_IRQ IMXRT_IRQ_ENET2 |
| # define IMXRT_ENETN_BASE IMXRT_ENET2_BASE |
| # if defined(CONFIG_IMXRT_MAC_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR1_ENET2_TX_DIR_OUT |
| # define GPR_ENET_CLK_SEL 0 |
| # endif |
| # if defined(CONFIG_IMXRT_PHY_PROVIDES_TXC) |
| # define GPR_ENET_TX_DIR GPR_GPR1_ENET2_TX_DIR_IN |
| # define GPR_ENET_CLK_SEL GPR_GPR1_ENET2_CLK_SEL |
| # endif |
| # endif |
| #endif |
| |
| #if CONFIG_IMXRT_ENET_NTXBUFFERS < 1 |
| # error "Need at least one TX buffer" |
| #endif |
| |
| #if CONFIG_IMXRT_ENET_NRXBUFFERS < 1 |
| # error "Need at least one RX buffer" |
| #endif |
| |
| /* From ref manual TDSR/RDSR description |
| * For optimal performance the pointer should be 512-bit aligned, that is, |
| * evenly divisible by 64. |
| */ |
| |
| #define ENET_ALIGN 64 |
| #define ENET_ALIGN_MASK (ENET_ALIGN - 1) |
| #define ENET_ALIGN_UP(n) (((n) + ENET_ALIGN_MASK) & ~ENET_ALIGN_MASK) |
| |
| #define DESC_SIZE sizeof(struct enet_desc_s) |
| |
| #define ALIGNED_BUFSIZE ENET_ALIGN_UP(CONFIG_NET_ETH_PKTSIZE + \ |
| CONFIG_NET_GUARDSIZE) |
| #define NENET_NBUFFERS \ |
| (CONFIG_IMXRT_ENET_NTXBUFFERS + CONFIG_IMXRT_ENET_NRXBUFFERS) |
| |
| /* TX timeout = 1 minute */ |
| |
| #define IMXRT_TXTIMEOUT (60 * CLK_TCK) |
| #define MII_MAXPOLLS (0x1ffff) |
| #define LINK_WAITUS (500 * 1000) |
| #define LINK_NLOOPS (10) |
| |
| /* PHY definitions. |
| * |
| * The selected PHY must be selected from the drivers/net/Kconfig PHY menu. |
| * A description of the PHY must be provided here. That description must |
| * include: |
| * |
| * 1. BOARD_PHY_NAME: A PHY name string (for debug output), |
| * 2. BOARD_PHYID1 and BOARD_PHYID2: The PHYID1 and PHYID2 values (from |
| * include/nuttx/net/mii.h) |
| * 3. BOARD_PHY_STATUS: The address of the status register to use when |
| * querying link status (from include/nuttx/net/mii.h) |
| * 4. BOARD_PHY_ADDRThe PHY broadcast address of 0 is selected. This |
| * should be fine as long as there is only a single PHY. |
| * 5. BOARD_PHY_10BASET: A macro that can convert the status register |
| * value into a boolean: true=10Base-T, false=Not 10Base-T |
| * 6. BOARD_PHY_100BASET: A macro that can convert the status register |
| * value into a boolean: true=100Base-T, false=Not 100Base-T |
| * 7. BOARD_PHY_ISDUPLEX: A macro that can convert the status register |
| * value into a boolean: true=duplex mode, false=half-duplex mode |
| * |
| * The imxrt1050-evk board uses a KSZ8081 PHY |
| * The Versiboard2 uses a LAN8720 PHY |
| * The Teensy-4.1 board uses a DP83825I PHY |
| * |
| * ...and further PHY descriptions here. |
| */ |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| # if !defined(BOARD_ETH0_PHY_LIST) |
| # error "CONFIG_ETH0_PHY_MULTI requires board.h to define BOARD_ETH0_PHY_LIST!" |
| # endif |
| # define BOARD_PHY_NAME g_board_phys[priv->current_phy].name |
| # define BOARD_PHYID1 g_board_phys[priv->current_phy].id1 |
| # define BOARD_PHYID2 g_board_phys[priv->current_phy].id2 |
| # define BOARD_PHY_STATUS g_board_phys[priv->current_phy].status |
| # define BOARD_PHY_ADDR priv->current_phy_address |
| # define BOARD_PHY_10BASET(s) (imxrt_phy_status(priv, (s), g_board_phys[priv->current_phy].mbps10) != 0) |
| # define BOARD_PHY_100BASET(s) (imxrt_phy_status(priv, (s), g_board_phys[priv->current_phy].mbps100) != 0) |
| # define BOARD_PHY_ISDUPLEX(s) (imxrt_phy_status(priv, (s), g_board_phys[priv->current_phy].duplex) != 0) |
| # define BOARD_PHY_ISCLAUSE45() (g_board_phys[priv->current_phy].clause == 45) |
| # define CLAUSE45 |
| # define MMD1 1 |
| # define MMD1_PMA_STATUS1 1 |
| # define MMD1_PS1_RECEIVE_LINK_STATUS (1 << 2) |
| #elif defined(CONFIG_ETH0_PHY_KSZ8081) |
| # define BOARD_PHY_NAME MII_KSZ8081_NAME |
| # define BOARD_PHYID1 MII_PHYID1_KSZ8081 |
| # define BOARD_PHYID2 MII_PHYID2_KSZ8081 |
| # define BOARD_PHY_STATUS MII_KSZ8081_PHYCTRL1 |
| # define BOARD_PHY_ADDR (0) |
| # define BOARD_PHY_10BASET(s) (((s) & MII_PHYCTRL1_MODE_10HDX) != 0) |
| # define BOARD_PHY_100BASET(s) (((s) & MII_PHYCTRL1_MODE_100HDX) != 0) |
| # define BOARD_PHY_ISDUPLEX(s) (((s) & MII_PHYCTRL1_MODE_DUPLEX) != 0) |
| #elif defined(CONFIG_ETH0_PHY_LAN8720) |
| # define BOARD_PHY_NAME MII_LAN8720_NAME |
| # define BOARD_PHYID1 MII_PHYID1_LAN8720 |
| # define BOARD_PHYID2 MII_PHYID2_LAN8720 |
| # define BOARD_PHY_STATUS MII_LAN8720_SCSR |
| # define BOARD_PHY_ADDR (1) |
| # define BOARD_PHY_10BASET(s) (((s)&MII_LAN8720_SPSCR_10MBPS) != 0) |
| # define BOARD_PHY_100BASET(s) (((s)&MII_LAN8720_SPSCR_100MBPS) != 0) |
| # define BOARD_PHY_ISDUPLEX(s) (((s)&MII_LAN8720_SPSCR_DUPLEX) != 0) |
| #elif defined(CONFIG_ETH0_PHY_LAN8742A) |
| # define BOARD_PHY_NAME MII_LAN8742A_NAME |
| # define BOARD_PHYID1 MII_PHYID1_LAN8742A |
| # define BOARD_PHYID2 MII_PHYID2_LAN8742A |
| # define BOARD_PHY_STATUS MII_LAN8740_SCSR |
| # define BOARD_PHY_ADDR (0) |
| # define BOARD_PHY_10BASET(s) (((s)&MII_LAN8720_SPSCR_10MBPS) != 0) |
| # define BOARD_PHY_100BASET(s) (((s)&MII_LAN8720_SPSCR_100MBPS) != 0) |
| # define BOARD_PHY_ISDUPLEX(s) (((s)&MII_LAN8720_SPSCR_DUPLEX) != 0) |
| #elif defined(CONFIG_ETH0_PHY_DP83825I) |
| # define BOARD_PHY_NAME MII_DP83825I_NAME |
| # define BOARD_PHYID1 MII_PHYID1_DP83825I |
| # define BOARD_PHYID2 MII_PHYID2_DP83825I |
| # define BOARD_PHY_STATUS MII_DP83825I_PHYSTS |
| # define BOARD_PHY_ADDR (0) |
| # define BOARD_PHY_10BASET(s) (((s) & MII_DP83825I_PHYSTS_SPEED) != 0) |
| # define BOARD_PHY_100BASET(s) (((s) & MII_DP83825I_PHYSTS_SPEED) == 0) |
| # define BOARD_PHY_ISDUPLEX(s) (((s) & MII_DP83825I_PHYSTS_DUPLEX) != 0) |
| #elif defined(CONFIG_ETH0_PHY_TJA1103) |
| # define BOARD_PHY_NAME MII_TJA1103_NAME |
| # define BOARD_PHYID1 MII_PHYID1_TJA1103 |
| # define BOARD_PHYID2 MII_PHYID2_TJA1103 |
| # define BOARD_PHY_STATUS MII_TJA110X_BSR |
| # define BOARD_PHY_10BASET(s) 0 /* PHY only supports 100BASE-T1 */ |
| # define BOARD_PHY_100BASET(s) 1 /* PHY only supports 100BASE-T1 */ |
| # define BOARD_PHY_ISDUPLEX(s) 1 /* PHY only supports fullduplex */ |
| |
| # define CLAUSE45 1 |
| # define MMD1 1 |
| # define MMD1_PMA_STATUS1 1 |
| # define MMD1_PS1_RECEIVE_LINK_STATUS (1 << 2) |
| #elif defined(CONFIG_ETH0_PHY_YT8512) |
| # define BOARD_PHY_NAME MII_YT8512_NAME |
| # define BOARD_PHYID1 MII_PHYID1_YT8512 |
| # define BOARD_PHYID2 MII_PHYID2_YT8512 |
| # define BOARD_PHY_STATUS MII_YT8512_PHYSTS |
| # define BOARD_PHY_ADDR (0) |
| # define BOARD_PHY_10BASET(s) (((s) & MII_YT8512_PHYSTS_SPEED) == 0) |
| # define BOARD_PHY_100BASET(s) (((s) & MII_YT8512_PHYSTS_SPEED) != 0) |
| # define BOARD_PHY_ISDUPLEX(s) (((s) & MII_YT8512_PHYSTS_DUPLEX) != 0) |
| #else |
| # error "Unrecognized or missing PHY selection" |
| #endif |
| |
| /* Estimate the MII_SPEED in order to get an MDC close to 2.5MHz, |
| * based on the internal module (ENET) clock: |
| * |
| * MII_SPEED = ENET_FREQ/5000000 -1 |
| * |
| * For example, if ENET_FREQ_MHZ=120 (MHz): |
| * |
| * MII_SPEED = 120000000/5000000 -1 |
| * = 23 |
| */ |
| |
| #ifdef CONFIG_ARCH_FAMILY_IMXRT117x |
| # define IMXRT_MII_SPEED 0x2f /* 100Mbs. Revisit and remove hardcoded value */ |
| #else |
| # define IMXRT_MII_SPEED 0x38 /* 100Mbs. Revisit and remove hardcoded value */ |
| #endif |
| #if IMXRT_MII_SPEED > 63 |
| # error "IMXRT_MII_SPEED is out-of-range" |
| #endif |
| |
| /* Interrupt groups */ |
| |
| #define RX_INTERRUPTS (ENET_INT_RXF | ENET_INT_RXB) |
| #define TX_INTERRUPTS ENET_INT_TXF |
| #define ERROR_INTERRUPTS (ENET_INT_UN | ENET_INT_RL | ENET_INT_LC | \ |
| ENET_INT_EBERR | ENET_INT_BABT | ENET_INT_BABR) |
| |
| /* The subset of errors that require us to reset the hardware - this list |
| * may need to be revisited if it's found that some error above leads to a |
| * locking up of the Ethernet interface. |
| */ |
| |
| #define CRITICAL_ERROR (ENET_INT_UN | ENET_INT_RL | ENET_INT_EBERR) |
| |
| /* This is a helper pointer for accessing |
| * the contents of the Ethernet header |
| */ |
| |
| #define BUF ((struct eth_hdr_s *)priv->dev.d_buf) |
| |
| /**************************************************************************** |
| * Private Types |
| ****************************************************************************/ |
| |
| /* The imxrt_driver_s encapsulates all state information for |
| * a single hardware interface |
| */ |
| |
| struct imxrt_driver_s |
| { |
| uint32_t base; /* Base address of ENET controller */ |
| bool bifup; /* true:ifup false:ifdown */ |
| uint8_t txtail; /* The oldest busy TX descriptor */ |
| uint8_t txhead; /* The next TX descriptor to use */ |
| uint8_t rxtail; /* The next RX descriptor to use */ |
| uint8_t phyaddr; /* Selected PHY address */ |
| struct wdog_s txtimeout; /* TX timeout timer */ |
| uint32_t ints; /* Enabled interrupts */ |
| struct work_s irqwork; /* For deferring interrupt work to the work queue */ |
| struct work_s pollwork; /* For deferring poll work to the work queue */ |
| struct enet_desc_s *txdesc; /* A pointer to the list of TX descriptor */ |
| struct enet_desc_s *rxdesc; /* A pointer to the list of RX descriptors */ |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| uint8_t current_phy; /* The index of the PHY being used */ |
| uint8_t current_phy_address; /* The address of the PHY being used */ |
| #endif |
| /* This holds the information visible to the NuttX network */ |
| |
| struct net_driver_s dev; /* Interface understood by the network */ |
| }; |
| |
| /**************************************************************************** |
| * Private Data |
| ****************************************************************************/ |
| |
| /* BOARD_ETH0_PHY_LIST provided by the board.h for CONFIG_ETH0_PHY_MULTI */ |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| const struct phy_desc_s g_board_phys[] = |
| { |
| BOARD_ETH0_PHY_LIST |
| }; |
| #endif |
| |
| static struct imxrt_driver_s g_enet[CONFIG_IMXRT_ENET_NETHIFS]; |
| |
| /* The DMA descriptors */ |
| |
| static struct enet_desc_s g_desc_pool[NENET_NBUFFERS] |
| aligned_data(ENET_ALIGN); |
| |
| /* The DMA buffers */ |
| |
| static uint8_t g_buffer_pool[NENET_NBUFFERS][ALIGNED_BUFSIZE] |
| aligned_data(ENET_ALIGN); |
| |
| /**************************************************************************** |
| * Private Function Prototypes |
| ****************************************************************************/ |
| |
| /* Utility functions */ |
| |
| static inline uint32_t imxrt_enet_getreg32(struct imxrt_driver_s *priv, |
| uint32_t offset); |
| static inline void imxrt_enet_putreg32(struct imxrt_driver_s *priv, |
| uint32_t value, uint32_t offset); |
| |
| static inline void imxrt_enet_modifyreg32(struct imxrt_driver_s *priv, |
| unsigned int offset, |
| uint32_t clearbits, |
| uint32_t setbits); |
| |
| #ifndef IMXRT_BUFFERS_SWAP |
| # define imxrt_swap32(value) (value) |
| # define imxrt_swap16(value) (value) |
| #else |
| #if 0 /* Use builtins if the compiler supports them */ |
| static inline uint32_t imxrt_swap32(uint32_t value); |
| static inline uint16_t imxrt_swap16(uint16_t value); |
| #else |
| # define imxrt_swap32 __builtin_bswap32 |
| # define imxrt_swap16 __builtin_bswap16 |
| #endif |
| #endif |
| |
| /* Common TX logic */ |
| |
| static bool imxrt_txringfull(struct imxrt_driver_s *priv); |
| static int imxrt_transmit(struct imxrt_driver_s *priv); |
| static int imxrt_txpoll(struct net_driver_s *dev); |
| |
| /* Interrupt handling */ |
| |
| static void imxrt_dispatch(struct imxrt_driver_s *priv); |
| static void imxrt_receive(struct imxrt_driver_s *priv); |
| static void imxrt_txdone(struct imxrt_driver_s *priv); |
| |
| static void imxrt_enet_interrupt_work(void *arg); |
| static int imxrt_enet_interrupt(int irq, void *context, void *arg); |
| |
| /* Watchdog timer expirations */ |
| |
| static void imxrt_txtimeout_work(void *arg); |
| static void imxrt_txtimeout_expiry(wdparm_t arg); |
| |
| /* NuttX callback functions */ |
| |
| static int imxrt_ifup(struct net_driver_s *dev); |
| static int imxrt_ifdown(struct net_driver_s *dev); |
| |
| static void imxrt_txavail_work(void *arg); |
| static int imxrt_txavail(struct net_driver_s *dev); |
| |
| /* Internal ifup function that allows phy reset to be optional */ |
| |
| static int imxrt_ifup_action(struct net_driver_s *dev, bool resetphy); |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int imxrt_addmac(struct net_driver_s *dev, |
| const uint8_t *mac); |
| static int imxrt_rmmac(struct net_driver_s *dev, const uint8_t *mac); |
| #endif |
| |
| #ifdef CONFIG_NETDEV_IOCTL |
| static int imxrt_ioctl(struct net_driver_s *dev, int cmd, |
| unsigned long arg); |
| #endif |
| |
| /* PHY/MII support */ |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| static int imxrt_phy_is(struct imxrt_driver_s *priv, const char *name); |
| static int imxrt_phy_status(struct imxrt_driver_s *priv, int phydata, |
| uint16_t mask); |
| static int imxrt_determine_phy(struct imxrt_driver_s *priv); |
| #endif |
| |
| #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) |
| static int imxrt_phyintenable(struct imxrt_driver_s *priv); |
| #endif |
| static inline void imxrt_initmii(struct imxrt_driver_s *priv); |
| static int imxrt_writemii(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t regaddr, uint16_t data); |
| static int imxrt_readmii(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t regaddr, uint16_t *data); |
| static int imxrt_initphy(struct imxrt_driver_s *priv, bool renogphy); |
| #if defined(CLAUSE45) |
| static int imxrt_readmmd(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t mmd, uint16_t regaddr, uint16_t *data); |
| #if 0 |
| static int imxrt_writemmd(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t mmd, uint16_t regaddr, uint16_t data); |
| #endif |
| #endif |
| /* Initialization */ |
| |
| static void imxrt_initbuffers(struct imxrt_driver_s *priv); |
| static void imxrt_reset(struct imxrt_driver_s *priv); |
| |
| /**************************************************************************** |
| * Private Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Name: imxrt_enet_getreg32 |
| * |
| * Description: |
| * Get the contents of the ENET register at offset |
| * |
| * Input Parameters: |
| * priv - private ENET device structure |
| * offset - offset to the register of interest |
| * |
| * Returned Value: |
| * The contents of the 32-bit register |
| * |
| ****************************************************************************/ |
| |
| static inline uint32_t imxrt_enet_getreg32(struct imxrt_driver_s *priv, |
| uint32_t offset) |
| { |
| return getreg32(priv->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Name: imxrt_enet_putreg32 |
| * |
| * Description: |
| * Atomically modify the specified bits in a memory mapped register |
| * |
| * Input Parameters: |
| * priv - private SPI device structure |
| * offset - offset to the register of interest |
| * clearbits - the 32-bit value to be written as 0s |
| * setbits - the 32-bit value to be written as 1s |
| * |
| * Returned Value: |
| * None |
| * |
| ****************************************************************************/ |
| |
| static inline void imxrt_enet_modifyreg32(struct imxrt_driver_s *priv, |
| unsigned int offset, |
| uint32_t clearbits, |
| uint32_t setbits) |
| { |
| modifyreg32(priv->base + offset, clearbits, setbits); |
| } |
| |
| /**************************************************************************** |
| * Name: imxrt_enet_putreg32 |
| * |
| * Description: |
| * Write a 16-bit value to the ENET register at offset |
| * |
| * Input Parameters: |
| * priv - private SPI device structure |
| * value - the 32-bit value to be written |
| * offset - offset to the register of interest |
| * |
| * Returned Value: |
| * The contents of the 32-bit register |
| * |
| ****************************************************************************/ |
| |
| static inline void imxrt_enet_putreg32(struct imxrt_driver_s *priv, |
| uint32_t value, uint32_t offset) |
| { |
| putreg32(value, priv->base + offset); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_swap16/32 |
| * |
| * Description: |
| * The descriptors are represented by structures Unfortunately, when the |
| * structures are overlaid on the data, the bytes are reversed because |
| * the underlying hardware writes the data in big-endian byte order. |
| * |
| * Input Parameters: |
| * value - The value to be byte swapped |
| * |
| * Returned Value: |
| * The byte swapped value |
| * |
| ****************************************************************************/ |
| |
| #if 0 /* Use builtins if the compiler supports them */ |
| #ifndef CONFIG_ENDIAN_BIG |
| static inline uint32_t imxrt_swap32(uint32_t value) |
| { |
| uint32_t result = 0; |
| |
| __asm__ __volatile__ |
| ( |
| "rev %0, %1" |
| :"=r" (result) |
| : "r"(value) |
| ); |
| return result; |
| } |
| |
| static inline uint16_t imxrt_swap16(uint16_t value) |
| { |
| uint16_t result = 0; |
| |
| __asm__ __volatile__ |
| ( |
| "revsh %0, %1" |
| :"=r" (result) |
| : "r"(value) |
| ); |
| return result; |
| } |
| #endif |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_txringfull |
| * |
| * Description: |
| * Check if all of the TX descriptors are in use. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * true is the TX ring is full; false if there are free slots at the |
| * head index. |
| * |
| ****************************************************************************/ |
| |
| static bool imxrt_txringfull(struct imxrt_driver_s *priv) |
| { |
| uint8_t txnext; |
| |
| /* Check if there is room in the hardware to hold another outgoing |
| * packet. The ring is full if incrementing the head pointer would |
| * collide with the tail pointer. |
| */ |
| |
| txnext = priv->txhead + 1; |
| if (txnext >= CONFIG_IMXRT_ENET_NTXBUFFERS) |
| { |
| txnext = 0; |
| } |
| |
| return priv->txtail == txnext; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_transmit |
| * |
| * Description: |
| * Start hardware transmission. Called either from the txdone interrupt |
| * handling or from watchdog based polling. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure |
| * |
| * Assumptions: |
| * May or may not be called from an interrupt handler. In either case, |
| * global interrupts are disabled, either explicitly or indirectly through |
| * interrupt handling logic. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_transmit(struct imxrt_driver_s *priv) |
| { |
| struct enet_desc_s *txdesc; |
| uint8_t *buf; |
| |
| /* Since this can be called from imxrt_receive, it is possible that |
| * the transmit queue is full, so check for that now. If this is the |
| * case, the outgoing packet will be dropped (e.g. an ARP reply) |
| */ |
| |
| if (imxrt_txringfull(priv)) |
| { |
| return -EBUSY; |
| } |
| |
| /* When we get here the TX descriptor should show that the previous |
| * transfer has completed. 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->txdesc[priv->txhead]; |
| priv->txhead++; |
| if (priv->txhead >= CONFIG_IMXRT_ENET_NTXBUFFERS) |
| { |
| priv->txhead = 0; |
| } |
| |
| #ifdef CONFIG_DEBUG_ASSERTIONS |
| up_invalidate_dcache((uintptr_t)txdesc, |
| (uintptr_t)txdesc + sizeof(struct enet_desc_s)); |
| |
| DEBUGASSERT(priv->txtail != priv->txhead && |
| (txdesc->status1 & TXDESC_R) == 0); |
| #endif |
| |
| /* Increment statistics */ |
| |
| NETDEV_TXPACKETS(&priv->dev); |
| |
| /* Setup the buffer descriptor for transmission: address=priv->dev.d_buf, |
| * length=priv->dev.d_len |
| */ |
| |
| txdesc->length = imxrt_swap16(priv->dev.d_len); |
| #ifdef CONFIG_IMXRT_ENET_ENHANCEDBD |
| txdesc->bdu = 0x00000000; |
| txdesc->status2 = TXDESC_INT | TXDESC_TS; /* | TXDESC_IINS | TXDESC_PINS; */ |
| #endif |
| txdesc->status1 |= (TXDESC_R | TXDESC_L | TXDESC_TC); |
| |
| buf = (uint8_t *)imxrt_swap32((uint32_t)priv->dev.d_buf); |
| |
| struct enet_desc_s *rxdesc = &priv->rxdesc[priv->rxtail]; |
| |
| up_invalidate_dcache((uintptr_t)rxdesc, |
| (uintptr_t)rxdesc + sizeof(struct enet_desc_s)); |
| |
| if (rxdesc->data == buf) |
| { |
| /* Data was written into the RX buffer, so swap the TX and RX buffers */ |
| |
| DEBUGASSERT((rxdesc->status1 & RXDESC_E) == 0); |
| rxdesc->data = txdesc->data; |
| txdesc->data = buf; |
| up_clean_dcache((uintptr_t)rxdesc, |
| (uintptr_t)rxdesc + sizeof(struct enet_desc_s)); |
| } |
| else |
| { |
| DEBUGASSERT(txdesc->data == buf); |
| } |
| |
| #ifdef CONFIG_ARMV7M_DCACHE_WRITETHROUGH |
| /* Make sure that descriptors are flushed */ |
| |
| ARM_DSB(); |
| #else |
| up_clean_dcache((uintptr_t)txdesc, |
| (uintptr_t)txdesc + sizeof(struct enet_desc_s)); |
| |
| up_clean_dcache((uintptr_t)priv->dev.d_buf, |
| (uintptr_t)priv->dev.d_buf + priv->dev.d_len); |
| #endif |
| |
| /* Start the TX transfer (if it was not already waiting for buffers) */ |
| |
| imxrt_enet_putreg32(priv, ENET_TDAR, IMXRT_ENET_TDAR_OFFSET); |
| |
| /* Enable TX interrupts */ |
| |
| priv->ints |= TX_INTERRUPTS; |
| imxrt_enet_modifyreg32(priv, IMXRT_ENET_EIMR_OFFSET, 0, TX_INTERRUPTS); |
| |
| /* Setup the TX timeout watchdog (perhaps restarting the timer) */ |
| |
| wd_start(&priv->txtimeout, IMXRT_TXTIMEOUT, |
| imxrt_txtimeout_expiry, (wdparm_t)priv); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_txpoll |
| * |
| * Description: |
| * The transmitter is available, check if the network has any outgoing |
| * packets ready to send. This is a callback from devif_poll(). |
| * devif_poll() may be called: |
| * |
| * 1. When the preceding TX packet send is complete, |
| * 2. When the preceding TX packet send timesout and the interface is reset |
| * 3. During normal TX polling |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * OK on success; a negated errno on failure |
| * |
| * Assumptions: |
| * May or may not be called from an interrupt handler. In either case, |
| * global interrupts are disabled, either explicitly or indirectly through |
| * interrupt handling logic. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_txpoll(struct net_driver_s *dev) |
| { |
| struct imxrt_driver_s *priv = |
| (struct imxrt_driver_s *)dev->d_private; |
| |
| /* Send the packet */ |
| |
| imxrt_transmit(priv); |
| priv->dev.d_buf = (uint8_t *) |
| imxrt_swap32((uint32_t)priv->txdesc[priv->txhead].data); |
| |
| /* Check if there is room in the device to hold another packet. If |
| * not, return a non-zero value to terminate the poll. |
| */ |
| |
| if (imxrt_txringfull(priv)) |
| { |
| return -EBUSY; |
| } |
| |
| /* If zero is returned, the polling will continue until |
| * all connections have been examined. |
| */ |
| |
| return 0; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_dispatch |
| * |
| * Description: |
| * A new Rx packet was received; dispatch that packet to the network layer |
| * as necessary. |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Global interrupts are disabled by interrupt handling logic. |
| * |
| ****************************************************************************/ |
| |
| static inline void imxrt_dispatch(struct imxrt_driver_s *priv) |
| { |
| /* Update statistics */ |
| |
| NETDEV_RXPACKETS(&priv->dev); |
| |
| #ifdef CONFIG_NET_PKT |
| /* When packet sockets are enabled, feed the frame into the tap */ |
| |
| pkt_input(&priv->dev); |
| #endif |
| |
| #ifdef CONFIG_NET_IPv4 |
| /* Check for an IPv4 packet */ |
| |
| if (BUF->type == HTONS(ETHTYPE_IP)) |
| { |
| ninfo("IPv4 frame\n"); |
| NETDEV_RXIPV4(&priv->dev); |
| |
| /* Receive an IPv4 packet from the network device */ |
| |
| ipv4_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, d_len field will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* And send the packet */ |
| |
| imxrt_transmit(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_IPv6 |
| /* Check for an IPv6 packet */ |
| |
| if (BUF->type == HTONS(ETHTYPE_IP6)) |
| { |
| ninfo("IPv6 frame\n"); |
| NETDEV_RXIPV6(&priv->dev); |
| |
| /* Give the IPv6 packet to the network layer */ |
| |
| ipv6_input(&priv->dev); |
| |
| /* If the above function invocation resulted in data that should be |
| * sent out on the network, d_len field will set to a value > 0. |
| */ |
| |
| if (priv->dev.d_len > 0) |
| { |
| /* And send the packet */ |
| |
| imxrt_transmit(priv); |
| } |
| } |
| else |
| #endif |
| #ifdef CONFIG_NET_ARP |
| /* Check for an ARP packet */ |
| |
| if (BUF->type == HTONS(ETHTYPE_ARP)) |
| { |
| NETDEV_RXARP(&priv->dev); |
| 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) |
| { |
| imxrt_transmit(priv); |
| } |
| } |
| #endif |
| else |
| { |
| NETDEV_RXDROPPED(&priv->dev); |
| } |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_receive |
| * |
| * Description: |
| * An interrupt was received indicating the availability of a new RX packet |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Global interrupts are disabled by interrupt handling logic. |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_receive(struct imxrt_driver_s *priv) |
| { |
| struct enet_desc_s *rxdesc; |
| bool received; |
| |
| /* Loop while there are received packets to be processed */ |
| |
| do |
| { |
| /* Invalidate the Rx descriptor. Since it has been modified via DMA, |
| * we must assure that we must invalid any cached values and re-read |
| * the descriptor from the memory. |
| */ |
| |
| rxdesc = &priv->rxdesc[priv->rxtail]; |
| up_invalidate_dcache((uintptr_t)rxdesc, |
| (uintptr_t)rxdesc + sizeof(struct enet_desc_s)); |
| |
| /* Check if the data buffer associated with the descriptor has |
| * been filled with valid data. |
| */ |
| |
| received = ((rxdesc->status1 & RXDESC_E) == 0); |
| if (received) |
| { |
| /* Copy the buffer pointer to priv->dev.d_buf. Set amount of data |
| * in priv->dev.d_len |
| */ |
| |
| priv->dev.d_len = imxrt_swap16(rxdesc->length); |
| priv->dev.d_buf = (uint8_t *)imxrt_swap32((uint32_t)rxdesc->data); |
| |
| /* Invalidate the buffer so that the correct packet will be re-read |
| * from memory when the packet content is accessed. |
| */ |
| |
| up_invalidate_dcache((uintptr_t)priv->dev.d_buf, |
| (uintptr_t)priv->dev.d_buf + priv->dev.d_len); |
| |
| /* Dispatch (or drop) the newly received packet */ |
| |
| imxrt_dispatch(priv); |
| |
| /* Point the packet buffer back to the next Tx buffer that will be |
| * used during the next write. If the write queue is full, then |
| * this will point at an active buffer, which must not be written |
| * to. This is OK because devif_poll won't be called unless the |
| * queue is not full. |
| */ |
| |
| priv->dev.d_buf = (uint8_t *) |
| imxrt_swap32((uint32_t)priv->txdesc[priv->txhead].data); |
| rxdesc->status1 |= RXDESC_E; |
| |
| #ifdef CONFIG_ARMV7M_DCACHE_WRITETHROUGH |
| ARM_DSB(); |
| #else |
| up_clean_dcache((uintptr_t)rxdesc, |
| (uintptr_t)rxdesc + sizeof(struct enet_desc_s)); |
| #endif |
| |
| /* Update the index to the next descriptor */ |
| |
| priv->rxtail++; |
| if (priv->rxtail >= CONFIG_IMXRT_ENET_NRXBUFFERS) |
| { |
| priv->rxtail = 0; |
| } |
| |
| /* Indicate that there have been empty receive buffers produced */ |
| |
| imxrt_enet_putreg32(priv, ENET_RDAR, IMXRT_ENET_RDAR_OFFSET); |
| } |
| } |
| while (received); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_txdone |
| * |
| * Description: |
| * An interrupt was received indicating that the last TX packet(s) is done |
| * |
| * Input Parameters: |
| * priv - Reference to the driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Global interrupts are disabled by the watchdog logic. |
| * The network is locked. |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_txdone(struct imxrt_driver_s *priv) |
| { |
| struct enet_desc_s *txdesc; |
| bool txdone; |
| |
| /* We are here because a transmission completed, so the watchdog can be |
| * canceled. |
| */ |
| |
| wd_cancel(&priv->txtimeout); |
| |
| /* Verify that the oldest descriptor descriptor completed */ |
| |
| do |
| { |
| /* Invalidate the Tx descriptor. Since status information has been |
| * modified via DMA, we must assure that we must invalid any cached |
| * values and re-read the descriptor from the memory. |
| */ |
| |
| txdesc = &priv->txdesc[priv->txtail]; |
| up_invalidate_dcache((uintptr_t)txdesc, |
| (uintptr_t)txdesc + sizeof(struct enet_desc_s)); |
| |
| txdone = false; |
| if ((txdesc->status1 & TXDESC_R) == 0 && priv->txtail != priv->txhead) |
| { |
| /* Yes.. bump up the tail pointer, making space for a new TX |
| * descriptor. |
| */ |
| |
| priv->txtail++; |
| if (priv->txtail >= CONFIG_IMXRT_ENET_NTXBUFFERS) |
| { |
| priv->txtail = 0; |
| } |
| |
| /* Update statistics */ |
| |
| NETDEV_TXDONE(&priv->dev); |
| txdone = true; |
| } |
| } |
| while (txdone); |
| |
| /* Are there other transmissions queued? */ |
| |
| if (priv->txtail == priv->txhead) |
| { |
| /* No.. Cancel the TX timeout and disable further Tx interrupts. */ |
| |
| wd_cancel(&priv->txtimeout); |
| |
| priv->ints &= ~TX_INTERRUPTS; |
| imxrt_enet_modifyreg32(priv, IMXRT_ENET_EIMR_OFFSET, TX_INTERRUPTS, |
| priv->ints); |
| } |
| |
| /* There should be space for a new TX in any event. Poll the network for |
| * new XMIT data |
| */ |
| |
| devif_poll(&priv->dev, imxrt_txpoll); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_enet_interrupt_work |
| * |
| * Description: |
| * Perform interrupt related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() was called. |
| * |
| * Returned Value: |
| * OK on success |
| * |
| * Assumptions: |
| * The network is locked. |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_enet_interrupt_work(void *arg) |
| { |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; |
| uint32_t pending; |
| #ifdef CONFIG_NET_MCASTGROUP |
| uint32_t gaurstore; |
| uint32_t galrstore; |
| #endif |
| |
| /* Process pending Ethernet interrupts */ |
| |
| net_lock(); |
| |
| /* Get the set of unmasked, pending interrupt. */ |
| |
| pending = imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & priv->ints; |
| |
| /* Clear the pending interrupts */ |
| |
| imxrt_enet_putreg32(priv, pending, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Check for errors */ |
| |
| if (pending & ERROR_INTERRUPTS) |
| { |
| /* An error has occurred, update statistics */ |
| |
| NETDEV_ERRORS(&priv->dev); |
| |
| nerr("pending %" PRIx32 " ints %" PRIx32 "\n", pending, priv->ints); |
| } |
| |
| if (pending & CRITICAL_ERROR) |
| { |
| nerr("Critical error, restarting Ethernet interface\n"); |
| |
| /* Bring the Ethernet chip down and back up but with no need to |
| * reset/renegotiate the phy. |
| */ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| /* Just before we pull the rug lets make sure we retain the |
| * multicast hash table. |
| */ |
| |
| gaurstore = imxrt_enet_getreg32(priv, IMXRT_ENET_GAUR_OFFSET); |
| galrstore = imxrt_enet_getreg32(priv, IMXRT_ENET_GALR_OFFSET); |
| #endif |
| |
| imxrt_ifdown(&priv->dev); |
| imxrt_ifup_action(&priv->dev, false); |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| /* Now write the multicast table back */ |
| |
| imxrt_enet_putreg32(priv, gaurstore, IMXRT_ENET_GAUR_OFFSET); |
| imxrt_enet_putreg32(priv, galrstore, IMXRT_ENET_GALR_OFFSET); |
| #endif |
| |
| /* Then poll the network for new XMIT data */ |
| |
| devif_poll(&priv->dev, imxrt_txpoll); |
| } |
| else |
| { |
| /* Check for the receipt of a packet */ |
| |
| if ((pending & ENET_INT_RXF) != 0) |
| { |
| /* A packet has been received, call imxrt_receive() to handle the |
| * packet. |
| */ |
| |
| imxrt_receive(priv); |
| } |
| |
| /* Check if a packet transmission has completed */ |
| |
| if ((pending & ENET_INT_TXF) != 0) |
| { |
| /* Call imxrt_txdone to handle the end of transfer even. NOTE |
| * that this may disable further Tx interrupts if there are no |
| * pending transmissions. |
| */ |
| |
| imxrt_txdone(priv); |
| } |
| } |
| |
| net_unlock(); |
| |
| /* Re-enable Ethernet interrupts */ |
| |
| imxrt_enet_putreg32(priv, priv->ints, IMXRT_ENET_EIMR_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_enet_interrupt |
| * |
| * Description: |
| * Three interrupt sources will vector to this function: |
| * 1. Ethernet MAC transmit interrupt handler |
| * 2. Ethernet MAC receive interrupt handler |
| * 3. |
| * |
| * Input Parameters: |
| * irq - Number of the IRQ that generated the interrupt |
| * context - Interrupt register state save info (architecture-specific) |
| * |
| * Returned Value: |
| * OK on success |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_enet_interrupt(int irq, void *context, void *arg) |
| { |
| register struct imxrt_driver_s *priv = |
| (struct imxrt_driver_s *)arg; |
| |
| /* Disable further Ethernet interrupts. Because Ethernet interrupts are |
| * also disabled if the TX timeout event occurs, there can be no race |
| * condition here. |
| */ |
| |
| imxrt_enet_putreg32(priv, 0, IMXRT_ENET_EIMR_OFFSET); |
| |
| /* Schedule to perform the interrupt processing on the worker thread. */ |
| |
| work_queue(ETHWORK, &priv->irqwork, imxrt_enet_interrupt_work, priv, 0); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_txtimeout_work |
| * |
| * Description: |
| * Perform TX timeout related work from the worker thread |
| * |
| * Input Parameters: |
| * arg - The argument passed when work_queue() as called. |
| * |
| * Returned Value: |
| * OK on success |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_txtimeout_work(void *arg) |
| { |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; |
| |
| /* Increment statistics and dump debug info */ |
| |
| net_lock(); |
| nerr("Resetting interface\n"); |
| |
| NETDEV_TXTIMEOUTS(&priv->dev); |
| |
| /* Take the interface down and bring it back up. That is the most |
| * aggressive hardware reset. |
| */ |
| |
| imxrt_ifdown(&priv->dev); |
| imxrt_ifup_action(&priv->dev, false); |
| |
| /* Then poll the network for new XMIT data */ |
| |
| devif_poll(&priv->dev, imxrt_txpoll); |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_txtimeout_expiry |
| * |
| * Description: |
| * Our TX watchdog timed out. Called from the timer interrupt handler. |
| * The last TX never completed. Reset the hardware and start again. |
| * |
| * Input Parameters: |
| * arg - The argument |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * Global interrupts are disabled by the watchdog logic. |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_txtimeout_expiry(wdparm_t arg) |
| { |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; |
| |
| /* 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. |
| */ |
| |
| imxrt_enet_putreg32(priv, 0, IMXRT_ENET_EIMR_OFFSET); |
| priv->ints = 0; |
| |
| /* Schedule to perform the TX timeout processing on the worker thread, |
| * canceling any pending interrupt work. |
| */ |
| |
| work_queue(ETHWORK, &priv->irqwork, imxrt_txtimeout_work, priv, 0); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_ifup_action |
| * |
| * Description: |
| * Internal action routine to bring up the Ethernet interface |
| * which makes the resetting of the phy (which takes considerable time) |
| * optional. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * resetphy - Flag indicating if Phy is to be reset. If not then the |
| * phy configuration is just re-loaded into the ethernet |
| * interface |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_ifup_action(struct net_driver_s *dev, bool resetphy) |
| { |
| struct imxrt_driver_s *priv = |
| (struct imxrt_driver_s *)dev->d_private; |
| uint8_t *mac = dev->d_mac.ether.ether_addr_octet; |
| uint32_t regval; |
| int ret; |
| |
| ninfo("Bringing up: %u.%u.%u.%u\n", |
| ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr), |
| ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr)); |
| |
| /* Initialize ENET buffers */ |
| |
| imxrt_initbuffers(priv); |
| |
| /* Configure the MII interface */ |
| |
| imxrt_initmii(priv); |
| |
| /* Set the MAC address */ |
| |
| imxrt_enet_putreg32(priv, (mac[0] << 24) | (mac[1] << 16) | |
| (mac[2] << 8) | mac[3], IMXRT_ENET_PALR_OFFSET); |
| imxrt_enet_putreg32(priv, (mac[4] << 24) | (mac[5] << 16), |
| IMXRT_ENET_PAUR_OFFSET); |
| |
| /* Configure the PHY */ |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| ret = imxrt_determine_phy(priv); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to determine the PHY: %d\n", ret); |
| return ret; |
| } |
| #endif |
| |
| ret = imxrt_initphy(priv, resetphy); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to configure the PHY: %d\n", ret); |
| return ret; |
| } |
| |
| /* Handle promiscuous mode */ |
| |
| #ifdef CONFIG_NET_PROMISCUOUS |
| regval = imxrt_enet_getreg32(priv, IMXRT_ENET_RCR_OFFSET); |
| regval |= ENET_RCR_PROM; |
| imxrt_enet_putreg32(priv, regval, IMXRT_ENET_RCR_OFFSET); |
| #endif |
| |
| /* Select legacy of enhanced buffer descriptor format */ |
| |
| #ifdef CONFIG_IMXRT_ENET_ENHANCEDBD |
| imxrt_enet_putreg32(priv, ENET_ECR_EN1588, IMXRT_ENET_ECR_OFFSET); |
| #else |
| imxrt_enet_putreg32(priv, 0, IMXRT_ENET_ECR_OFFSET); |
| #endif |
| |
| /* Set the RX buffer size */ |
| |
| imxrt_enet_putreg32(priv, ALIGNED_BUFSIZE, IMXRT_ENET_MRBR_OFFSET); |
| |
| /* Point to the start of the circular RX buffer descriptor queue */ |
| |
| imxrt_enet_putreg32(priv, (uint32_t)priv->rxdesc, IMXRT_ENET_RDSR_OFFSET); |
| |
| /* Point to the start of the circular TX buffer descriptor queue */ |
| |
| imxrt_enet_putreg32(priv, (uint32_t)priv->txdesc, IMXRT_ENET_TDSR_OFFSET); |
| |
| /* And enable the MAC itself */ |
| |
| regval = imxrt_enet_getreg32(priv, IMXRT_ENET_ECR_OFFSET); |
| regval |= ENET_ECR_ETHEREN |
| #ifdef IMXRT_USE_DBSWAP |
| | ENET_ECR_DBSWP |
| #endif |
| ; |
| imxrt_enet_putreg32(priv, regval, IMXRT_ENET_ECR_OFFSET); |
| |
| #ifdef CONFIG_ARMV7M_DCACHE_WRITETHROUGH |
| /* Make sure that descriptors are flushed */ |
| |
| ARM_DSB(); |
| #endif |
| |
| /* Indicate that there have been empty receive buffers produced */ |
| |
| imxrt_enet_putreg32(priv, ENET_RDAR, IMXRT_ENET_RDAR_OFFSET); |
| |
| imxrt_enet_putreg32(priv, 0, IMXRT_ENET_EIMR_OFFSET); |
| |
| /* Clear all pending ENET interrupt */ |
| |
| imxrt_enet_putreg32(priv, 0xffffffff, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Mark the interrupt "up" and enable interrupts at the NVIC */ |
| |
| up_enable_irq(IMXRT_ENET_IRQ); |
| |
| #ifdef IMXRT_ENET_IRQ_2 |
| up_enable_irq(IMXRT_ENET_IRQ_2); |
| #endif |
| |
| #ifdef IMXRT_ENET_IRQ_3 |
| up_enable_irq(IMXRT_ENET_IRQ_3); |
| #endif |
| |
| priv->bifup = true; |
| |
| /* Enable RX and error interrupts at the controller (TX interrupts are |
| * still disabled). |
| */ |
| |
| priv->ints = RX_INTERRUPTS | ERROR_INTERRUPTS; |
| imxrt_enet_modifyreg32(priv, IMXRT_ENET_EIMR_OFFSET, TX_INTERRUPTS, |
| priv->ints); |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_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: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_ifup(struct net_driver_s *dev) |
| { |
| /* The externally available ifup action includes resetting the phy */ |
| |
| return imxrt_ifup_action(dev, true); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_ifdown |
| * |
| * Description: |
| * NuttX Callback: Stop the interface. |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_ifdown(struct net_driver_s *dev) |
| { |
| struct imxrt_driver_s *priv = |
| (struct imxrt_driver_s *)dev->d_private; |
| irqstate_t flags; |
| |
| ninfo("Taking down: %u.%u.%u.%u\n", |
| ip4_addr1(dev->d_ipaddr), ip4_addr2(dev->d_ipaddr), |
| ip4_addr3(dev->d_ipaddr), ip4_addr4(dev->d_ipaddr)); |
| |
| /* Flush and disable the Ethernet interrupts at the NVIC */ |
| |
| flags = enter_critical_section(); |
| |
| priv->ints = 0; |
| imxrt_enet_putreg32(priv, priv->ints, IMXRT_ENET_EIMR_OFFSET); |
| up_disable_irq(IMXRT_ENET_IRQ); |
| |
| #ifdef IMXRT_ENET_IRQ_2 |
| up_disable_irq(IMXRT_ENET_IRQ_2); |
| #endif |
| |
| #ifdef IMXRT_ENET_IRQ_3 |
| up_disable_irq(IMXRT_ENET_IRQ_3); |
| #endif |
| |
| /* Cancel the TX timeout timers */ |
| |
| wd_cancel(&priv->txtimeout); |
| |
| /* Put the EMAC in its reset, non-operational state. This should be |
| * a known configuration that will guarantee the imxrt_ifup() always |
| * successfully brings the interface back up. |
| */ |
| |
| imxrt_reset(priv); |
| |
| /* Configure the MII interface */ |
| |
| imxrt_initmii(priv); |
| |
| /* Mark the device "down" */ |
| |
| priv->bifup = false; |
| leave_critical_section(flags); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_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 imxrt_txavail_work(void *arg) |
| { |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)arg; |
| |
| /* Ignore the notification if the interface is not yet up */ |
| |
| net_lock(); |
| if (priv->bifup) |
| { |
| /* Check if there is room in the hardware to hold another outgoing |
| * packet. |
| */ |
| |
| if (!imxrt_txringfull(priv)) |
| { |
| /* No, there is space for another transfer. Poll the network for |
| * new XMIT data. |
| */ |
| |
| devif_poll(&priv->dev, imxrt_txpoll); |
| } |
| } |
| |
| net_unlock(); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_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 imxrt_txavail(struct net_driver_s *dev) |
| { |
| struct imxrt_driver_s *priv = |
| (struct imxrt_driver_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, imxrt_txavail_work, priv, 0); |
| } |
| |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_calcethcrc |
| * |
| * Description: |
| * Function to calculate the CRC used by IMXRT to check an Ethernet frame |
| * |
| * Input Parameters: |
| * data - the data to be checked |
| * length - length of the data |
| * |
| * Returned Value: |
| * crc32 |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static uint32_t imxrt_calcethcrc(const uint8_t *data, size_t length) |
| { |
| uint32_t crc = 0xffffffffu; |
| uint32_t count1 = 0; |
| uint32_t count2 = 0; |
| |
| /* Calculates the CRC-32 polynomial on the multicast group address. */ |
| |
| for (count1 = 0; count1 < length; count1++) |
| { |
| uint8_t c = data[count1]; |
| |
| for (count2 = 0; count2 < 0x08u; count2++) |
| { |
| if ((c ^ crc) & 1U) |
| { |
| crc >>= 1U; |
| c >>= 1U; |
| crc ^= 0xedb88320u; |
| } |
| else |
| { |
| crc >>= 1U; |
| c >>= 1U; |
| } |
| } |
| } |
| |
| return crc; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_enet_hash_index |
| * |
| * Description: |
| * Function to find the hash index for multicast address filter |
| * |
| * Input Parameters: |
| * mac - The MAC address |
| * |
| * Returned Value: |
| * hash index |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static uint32_t imxrt_enet_hash_index(const uint8_t *mac) |
| { |
| uint32_t crc; |
| uint32_t hashindex; |
| |
| ninfo("MAC: %02x:%02x:%02x:%02x:%02x:%02x\n", |
| mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); |
| |
| crc = imxrt_calcethcrc(mac, 6); |
| hashindex = (crc >> 26) & 0x3f; |
| |
| return hashindex; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_addmac |
| * |
| * Description: |
| * NuttX Callback: Add the specified MAC address to the hardware multicast |
| * address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be added |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int imxrt_addmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| uint32_t hashindex; |
| uint32_t temp; |
| uint32_t registeraddress; |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)dev->d_private; |
| |
| hashindex = imxrt_enet_hash_index(mac); |
| |
| /* Add the MAC address to the hardware multicast routing table */ |
| |
| if (hashindex > 31) |
| { |
| registeraddress = IMXRT_ENET_GAUR_OFFSET; |
| hashindex -= 32; |
| } |
| else |
| { |
| registeraddress = IMXRT_ENET_GALR_OFFSET; |
| } |
| |
| temp = imxrt_enet_getreg32(priv, registeraddress); |
| temp |= 1 << hashindex; |
| imxrt_enet_putreg32(priv, temp, registeraddress); |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_rmmac |
| * |
| * Description: |
| * NuttX Callback: Remove the specified MAC address from the hardware |
| * multicast address filtering |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * mac - The MAC address to be removed |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NET_MCASTGROUP |
| static int imxrt_rmmac(struct net_driver_s *dev, const uint8_t *mac) |
| { |
| uint32_t hashindex; |
| uint32_t temp; |
| uint32_t registeraddress; |
| struct imxrt_driver_s *priv = (struct imxrt_driver_s *)dev->d_private; |
| |
| /* Remove the MAC address from the hardware multicast routing table */ |
| |
| hashindex = imxrt_enet_hash_index(mac); |
| |
| if (hashindex > 31) |
| { |
| registeraddress = IMXRT_ENET_GAUR_OFFSET; |
| hashindex -= 32; |
| } |
| else |
| { |
| registeraddress = IMXRT_ENET_GALR_OFFSET; |
| } |
| |
| temp = imxrt_enet_getreg32(priv, registeraddress); |
| temp &= ~(1 << hashindex); |
| imxrt_enet_putreg32(priv, temp, registeraddress); |
| |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_ioctl |
| * |
| * Description: |
| * PHY ioctl command handler |
| * |
| * Input Parameters: |
| * dev - Reference to the NuttX driver state structure |
| * cmd - ioctl command |
| * arg - Argument accompanying the command |
| * |
| * Returned Value: |
| * Zero (OK) on success; a negated errno value on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| #ifdef CONFIG_NETDEV_IOCTL |
| static int imxrt_ioctl(struct net_driver_s *dev, int cmd, unsigned long arg) |
| { |
| #ifdef CONFIG_NETDEV_PHY_IOCTL |
| struct imxrt_driver_s *priv = |
| (struct imxrt_driver_s *)dev->d_private; |
| #endif |
| int ret; |
| |
| switch (cmd) |
| { |
| #ifdef CONFIG_NETDEV_PHY_IOCTL |
| #ifdef CONFIG_ARCH_PHY_INTERRUPT |
| case SIOCMIINOTIFY: /* Set up for PHY event notifications */ |
| { |
| struct mii_ioctl_notify_s *req = |
| (struct mii_ioctl_notify_s *)((uintptr_t)arg); |
| |
| ret = phy_notify_subscribe(dev->d_ifname, req->pid, &req->event); |
| if (ret == OK) |
| { |
| /* Enable PHY link up/down interrupts */ |
| |
| ret = imxrt_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 = priv->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); |
| #if defined(CLAUSE45) |
| if ( |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| BOARD_PHY_ISCLAUSE45() && |
| # endif |
| MII_MSR == req->reg_num) |
| { |
| ret = imxrt_readmmd(priv, req->phy_id, MMD1, MMD1_PMA_STATUS1, |
| &req->val_out); |
| } |
| else |
| #endif |
| { |
| ret = imxrt_readmii(priv, 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 = imxrt_writemii(priv, req->phy_id, req->reg_num, req->val_in); |
| } |
| break; |
| #endif /* CONFIG_NETDEV_PHY_IOCTL */ |
| |
| default: |
| ret = -ENOTTY; |
| break; |
| } |
| |
| return ret; |
| } |
| #endif /* CONFIG_NETDEV_IOCTL */ |
| |
| /**************************************************************************** |
| * Function: imxrt_phyintenable |
| * |
| * Description: |
| * Enable link up/down PHY interrupts. The interrupt protocol is like this: |
| * |
| * - Interrupt status is cleared when the interrupt is enabled. |
| * - Interrupt occurs. Interrupt is disabled (at the processor level) when |
| * is received. |
| * - Interrupt status is cleared when the interrupt is re-enabled. |
| * |
| * Input Parameters: |
| * priv - A reference to the private driver state structure |
| * |
| * Returned Value: |
| * OK on success; Negated errno (-ETIMEDOUT) on failure. |
| * |
| ****************************************************************************/ |
| |
| #if defined(CONFIG_NETDEV_PHY_IOCTL) && defined(CONFIG_ARCH_PHY_INTERRUPT) |
| static int imxrt_phyintenable(struct imxrt_driver_s *priv) |
| { |
| #if defined(CONFIG_ETH0_PHY_KSZ8051) || defined(CONFIG_ETH0_PHY_KSZ8061) || \ |
| defined(CONFIG_ETH0_PHY_KSZ8081) || defined(CONFIG_ETH0_PHY_DP83825I) || \ |
| defined(CONFIG_ETH0_YT8512) || defined(CONFIG_ETH0_PHY_MULTI) |
| |
| uint16_t phyval; |
| int ret; |
| |
| /* Compile time Kzxxxx defaults */ |
| |
| uint16_t mask = MII_KSZ80X1_INT_LDEN | MII_KSZ80X1_INT_LUEN; |
| uint8_t rreg = MII_KSZ8081_INT; |
| uint8_t wreg = rreg; |
| |
| /* Compile time YT8512 defaults */ |
| # if defined(CONFIG_ETH0_YT8512) |
| mask = MII_YT8512_IMR_LD_EN | MII_YT8512_IMR_LU_EN; |
| rreg = MII_YT8512_ISR; |
| wreg = MII_YT8512_IMR; |
| # endif |
| |
| /* Run time YT8512 defaults */ |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| if (imxrt_phy_is(priv, MII_YT8512_NAME)) |
| { |
| mask = MII_YT8512_IMR_LD_EN | MII_YT8512_IMR_LU_EN; |
| rreg = MII_YT8512_ISR; |
| wreg = MII_YT8512_IMR; |
| } |
| else if (!(imxrt_phy_is(priv, MII_KSZ8051_NAME) || |
| imxrt_phy_is(priv, MII_KSZ8061_NAME) || |
| imxrt_phy_is(priv, MII_KSZ8081_NAME) || |
| imxrt_phy_is(priv, MII_DP83825I_NAME))) |
| { |
| return -ENOSYS; |
| } |
| # endif |
| |
| /* Read the interrupt status register in order to clear any pending |
| * interrupts |
| */ |
| |
| ret = imxrt_readmii(priv, priv->phyaddr, rreg, &phyval); |
| if (ret == OK) |
| { |
| /* Enable link up/down interrupts */ |
| |
| ret = imxrt_writemii(priv, priv->phyaddr, wreg, mask); |
| } |
| |
| return ret; |
| #else |
| # error Unrecognized PHY |
| return -ENOSYS; |
| # endif |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_initmii |
| * |
| * Description: |
| * Configure the MII interface |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_initmii(struct imxrt_driver_s *priv) |
| { |
| /* Speed is based on the peripheral (bus) clock; hold time is 2 module |
| * clock. This hold time value may need to be increased on some platforms |
| */ |
| |
| imxrt_enet_putreg32(priv, ENET_MSCR_HOLDTIME_2CYCLES | |
| IMXRT_MII_SPEED << ENET_MSCR_MII_SPEED_SHIFT, |
| IMXRT_ENET_MSCR_OFFSET); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_writemii |
| * |
| * Description: |
| * Write a 16-bit value to a PHY register. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * phyaddr - The PHY address |
| * regaddr - The PHY register address |
| * data - The data to write to the PHY register |
| * |
| * Returned Value: |
| * Zero on success, a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_writemii(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t regaddr, uint16_t data) |
| { |
| int timeout; |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Initiate the MII Management write */ |
| |
| imxrt_enet_putreg32(priv, data | |
| 2 << ENET_MMFR_TA_SHIFT | |
| (uint32_t)regaddr << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| ENET_MMFR_OP_WRMII | |
| 1 << ENET_MMFR_ST_SHIFT, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| /* Check for a timeout */ |
| |
| if (timeout == MII_MAXPOLLS) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_reademii |
| * |
| * Description: |
| * Read a 16-bit value from a PHY register. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * phyaddr - The PHY address |
| * regaddr - The PHY register address |
| * data - A pointer to the location to return the data |
| * |
| * Returned Value: |
| * Zero on success, a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_readmii(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t regaddr, uint16_t *data) |
| { |
| int timeout; |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Initiate the MII Management read */ |
| |
| imxrt_enet_putreg32(priv, 2 << ENET_MMFR_TA_SHIFT | |
| (uint32_t)regaddr << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| ENET_MMFR_OP_RDMII | |
| 1 << ENET_MMFR_ST_SHIFT, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| /* Check for a timeout */ |
| |
| if (timeout >= MII_MAXPOLLS) |
| { |
| nerr("ERROR: Timed out waiting for transfer to complete\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* And return the MII data */ |
| |
| *data = (uint16_t)(imxrt_enet_getreg32(priv, IMXRT_ENET_MMFR_OFFSET) & |
| ENET_MMFR_DATA_MASK); |
| return OK; |
| } |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| /**************************************************************************** |
| * Function: imxrt_determine_phy |
| * |
| * Description: |
| * Uses the board.h supplied PHY list to determine which PHY |
| * is populated on this board. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * |
| * Returned Value: |
| * Zero on success, a -ENOENT errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_determine_phy(struct imxrt_driver_s *priv) |
| { |
| uint16_t phydata = 0xffff; |
| uint8_t phyaddr = 0; |
| uint8_t last_phyaddr = 0; |
| int retries; |
| int ret; |
| |
| for (priv->current_phy = 0; priv->current_phy < nitems(g_board_phys); |
| priv->current_phy++) |
| { |
| priv->current_phy_address = |
| (uint8_t) g_board_phys[priv->current_phy].address_lo; |
| last_phyaddr = g_board_phys[priv->current_phy].address_high == 0xffff ? |
| priv->current_phy_address : |
| (uint8_t) g_board_phys[priv->current_phy].address_high; |
| |
| for (phyaddr = priv->current_phy_address; phyaddr <= last_phyaddr; |
| phyaddr++) |
| { |
| retries = 0; |
| do |
| { |
| nxsig_usleep(100); |
| phydata = 0xffff; |
| ret = imxrt_readmii(priv, phyaddr, MII_PHYID1, &phydata); |
| } |
| while ((ret < 0 || phydata == 0xffff) && ++retries < 3); |
| |
| if (retries <= 3 && ret == 0 && |
| phydata == g_board_phys[priv->current_phy].id1) |
| { |
| do |
| { |
| nxsig_usleep(100); |
| phydata = 0xffff; |
| ret = imxrt_readmii(priv, phyaddr, MII_PHYID2, &phydata); |
| } |
| while ((ret < 0 || phydata == 0xffff) && ++retries < 3); |
| if (retries <= 3 && ret == 0 && |
| (phydata & 0xfff0) == |
| (g_board_phys[priv->current_phy].id2 & 0xfff0)) |
| { |
| return OK; |
| } |
| } |
| } |
| } |
| |
| return -ENOENT; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_phy_is |
| * |
| * Description: |
| * Compares the name with the current selected PHY's name |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * name - a pointer to comapre to. |
| * |
| * Returned Value: |
| * 1 on match, a 0 on no match. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_phy_is(struct imxrt_driver_s *priv, const char *name) |
| { |
| return strcmp(g_board_phys[priv->current_phy].name, name) == 0; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_phy_status |
| * |
| * Description: |
| * Compares the name with the current selected PHY's name. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * phydata - last read phy data - may be ignored if there is no |
| * status register defined by the current PHY. |
| * mask - A value to and with phydata if a status register is |
| * defined. Or the value retunred if no status register is |
| * defined. |
| * |
| * Returned Value: |
| * mask or (phydat & mask) |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_phy_status(struct imxrt_driver_s *priv, int phydata, |
| uint16_t mask) |
| { |
| int rv = mask; |
| if (g_board_phys[priv->current_phy].status != 0xffff) |
| { |
| rv &= phydata; |
| } |
| |
| return rv; |
| } |
| #endif |
| |
| #if 0 |
| #if defined(CLAUSE45) |
| /**************************************************************************** |
| * Function: imxrt_writemmd |
| * |
| * Description: |
| * Write a 16-bit value to a the selected MMD PHY register. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * phyaddr - The PHY address |
| * mmd - The Selected MMD Space |
| * regaddr - The PHY register address |
| * data - The data to write to the PHY register |
| * |
| * Returned Value: |
| * Zero on success, a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_writemmd(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t mmd, uint16_t regaddr, uint16_t data) |
| { |
| int timeout; |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Initiate the MMD Management write - Address Phase */ |
| |
| imxrt_enet_putreg32(priv, |
| 0 << ENET_MMFR_ST_SHIFT | |
| ENET_MMFR_OP_WRNOTMII | |
| (uint32_t)mmd << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| 2 << ENET_MMFR_TA_SHIFT | |
| regaddr, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Check for a timeout */ |
| |
| if (timeout == MII_MAXPOLLS) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| /* Initiate the MMD Management write - Data Phase */ |
| |
| imxrt_enet_putreg32(priv, |
| 0 << ENET_MMFR_ST_SHIFT | |
| ENET_MMFR_OP_WRMII | |
| (uint32_t)mmd << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| 2 << ENET_MMFR_TA_SHIFT | |
| data, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Check for a timeout */ |
| |
| if (timeout == MII_MAXPOLLS) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| return OK; |
| } |
| #endif |
| #endif |
| |
| #if defined(CLAUSE45) |
| /**************************************************************************** |
| * Function: imxrt_reademmd |
| * |
| * Description: |
| * Read a 16-bit value from a PHY register. |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * phyaddr - The PHY address |
| * mmd - The Selected MMD Space |
| * regaddr - The PHY register address |
| * data - A pointer to the location to return the data |
| * |
| * Returned Value: |
| * Zero on success, a negated errno value on failure. |
| * |
| ****************************************************************************/ |
| |
| static int imxrt_readmmd(struct imxrt_driver_s *priv, uint8_t phyaddr, |
| uint8_t mmd, uint16_t regaddr, uint16_t *data) |
| { |
| int timeout; |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Initiate the MMD Management read - Address Phase */ |
| |
| imxrt_enet_putreg32(priv, |
| 0 << ENET_MMFR_ST_SHIFT | |
| ENET_MMFR_OP_WRNOTMII | |
| (uint32_t)mmd << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| 2 << ENET_MMFR_TA_SHIFT | |
| regaddr, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Check for a timeout */ |
| |
| if (timeout >= MII_MAXPOLLS) |
| { |
| nerr("ERROR: Timed out waiting for transfer to complete\n"); |
| return -ETIMEDOUT; |
| } |
| |
| /* Initiate the MMD Management read - Data Phase */ |
| |
| imxrt_enet_putreg32(priv, |
| 0 << ENET_MMFR_ST_SHIFT | |
| ENET_MMFR_OP_RDNOTMII | |
| (uint32_t)mmd << ENET_MMFR_RA_SHIFT | |
| (uint32_t)phyaddr << ENET_MMFR_PA_SHIFT | |
| 2 << ENET_MMFR_TA_SHIFT, |
| IMXRT_ENET_MMFR_OFFSET); |
| |
| /* Wait for the transfer to complete */ |
| |
| for (timeout = 0; timeout < MII_MAXPOLLS; timeout++) |
| { |
| if ((imxrt_enet_getreg32(priv, IMXRT_ENET_EIR_OFFSET) & |
| ENET_INT_MII) != 0) |
| { |
| break; |
| } |
| } |
| |
| /* Clear the MII interrupt bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_INT_MII, IMXRT_ENET_EIR_OFFSET); |
| |
| /* Check for a timeout */ |
| |
| if (timeout == MII_MAXPOLLS) |
| { |
| return -ETIMEDOUT; |
| } |
| |
| /* And return the MII data */ |
| |
| *data = (uint16_t)(imxrt_enet_getreg32(priv, IMXRT_ENET_MMFR_OFFSET) & |
| ENET_MMFR_DATA_MASK); |
| return OK; |
| } |
| #endif |
| |
| /**************************************************************************** |
| * Function: imxrt_initphy |
| * |
| * Description: |
| * Configure the PHY |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * renogphy - Flag indicating if to perform negotiation of the link |
| * |
| * Returned Value: |
| * Zero (OK) returned on success; a negated errno value is returned on any |
| * failure; |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static inline int imxrt_initphy(struct imxrt_driver_s *priv, bool renogphy) |
| { |
| uint32_t rcr; |
| uint32_t tcr; |
| uint32_t racc; |
| uint16_t phydata; |
| uint8_t phyaddr = BOARD_PHY_ADDR; |
| int retries; |
| int ret; |
| |
| if (renogphy) |
| { |
| /* Loop (potentially infinitely?) until we successfully communicate |
| * with the PHY. This is 'standard stuff' that should work for any PHY |
| * - we are not communicating with it's 'special' registers |
| * at this point. |
| */ |
| |
| ninfo("%s: Try phyaddr: %u\n", BOARD_PHY_NAME, phyaddr); |
| |
| /* Try to read PHYID1 few times using this address */ |
| |
| retries = 0; |
| do |
| { |
| nxsig_usleep(LINK_WAITUS); |
| |
| ninfo("%s: Read PHYID1, retries=%d\n", |
| BOARD_PHY_NAME, retries + 1); |
| |
| phydata = 0xffff; |
| ret = imxrt_readmii(priv, phyaddr, MII_PHYID1, &phydata); |
| } |
| while ((ret < 0 || phydata == 0xffff) && ++retries < 3); |
| |
| if (retries >= 3) |
| { |
| nerr("ERROR: Failed to read %s PHYID1 at address %d\n", |
| BOARD_PHY_NAME, phyaddr); |
| return -ENOENT; |
| } |
| |
| ninfo("%s: Using PHY address %u\n", BOARD_PHY_NAME, phyaddr); |
| priv->phyaddr = phyaddr; |
| |
| /* Verify PHYID1. Compare OUI bits 3-18 */ |
| |
| ninfo("%s: PHYID1: %04x\n", BOARD_PHY_NAME, phydata); |
| if (phydata != BOARD_PHYID1) |
| { |
| nerr("ERROR: PHYID1=%04x incorrect for %s. Expected %04x\n", |
| phydata, BOARD_PHY_NAME, BOARD_PHYID1); |
| return -ENXIO; |
| } |
| |
| /* Read PHYID2 */ |
| |
| ret = imxrt_readmii(priv, phyaddr, MII_PHYID2, &phydata); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read %s PHYID2: %d\n", BOARD_PHY_NAME, ret); |
| return ret; |
| } |
| |
| ninfo("%s: PHYID2: %04x\n", BOARD_PHY_NAME, phydata); |
| |
| /* Verify PHYID2: Compare OUI bits 19-24 and the 6-bit model number |
| * (ignoring the 4-bit revision number). |
| */ |
| |
| if ((phydata & 0xfff0) != (BOARD_PHYID2 & 0xfff0)) |
| { |
| nerr("ERROR: PHYID2=%04x incorrect for %s. Expected %04x\n", |
| (phydata & 0xfff0), BOARD_PHY_NAME, (BOARD_PHYID2 & 0xfff0)); |
| return -ENXIO; |
| } |
| |
| #if defined(CONFIG_ETH0_PHY_KSZ8081) || defined(CONFIG_ETH0_PHY_MULTI) |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| if (imxrt_phy_is(priv, MII_KSZ8081_NAME)) |
| { |
| # endif |
| /* Reset PHY */ |
| |
| imxrt_writemii(priv, phyaddr, MII_MCR, MII_MCR_RESET); |
| |
| /* Set RMII mode */ |
| |
| ret = imxrt_readmii(priv, phyaddr, MII_KSZ8081_PHYCTRL2, &phydata); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read MII_KSZ8081_PHYCTRL2\n"); |
| return ret; |
| } |
| |
| /* Indicate 50MHz clock */ |
| |
| imxrt_writemii(priv, phyaddr, MII_KSZ8081_PHYCTRL2, |
| (phydata | (1 << 7))); |
| |
| /* Switch off NAND Tree mode (in case it was set via pinning) */ |
| |
| ret = imxrt_readmii(priv, phyaddr, MII_KSZ8081_OMSO, &phydata); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read MII_KSZ8081_OMSO: %d\n", ret); |
| return ret; |
| } |
| |
| imxrt_writemii(priv, phyaddr, MII_KSZ8081_OMSO, |
| (phydata & ~(1 << 5))); |
| |
| /* Set Ethernet led to green = activity and yellow = link and */ |
| |
| ret = imxrt_readmii(priv, phyaddr, MII_KSZ8081_PHYCTRL2, &phydata); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read MII_KSZ8081_PHYCTRL2\n"); |
| return ret; |
| } |
| |
| imxrt_writemii(priv, phyaddr, MII_KSZ8081_PHYCTRL2, |
| (phydata | (1 << 4))); |
| |
| imxrt_writemii(priv, phyaddr, MII_ADVERTISE, |
| MII_ADVERTISE_100BASETXFULL | |
| MII_ADVERTISE_100BASETXHALF | |
| MII_ADVERTISE_10BASETXFULL | |
| MII_ADVERTISE_10BASETXHALF | |
| MII_ADVERTISE_CSMA); |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| |
| # endif |
| #endif |
| #if defined (CONFIG_ETH0_PHY_LAN8720) || \ |
| defined (CONFIG_ETH0_PHY_LAN8742A) || \ |
| defined (CONFIG_ETH0_PHY_MULTI) |
| |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| if (imxrt_phy_is(priv, MII_LAN8720_NAME) || |
| imxrt_phy_is(priv, MII_LAN8742A_NAME)) |
| { |
| # endif |
| |
| /* Make sure that PHY comes up in correct mode when it's reset */ |
| |
| imxrt_writemii(priv, phyaddr, MII_LAN8720_MODES, |
| MII_LAN8720_MODES_RESV | MII_LAN8720_MODES_ALL | |
| MII_LAN8720_MODES_PHYAD(BOARD_PHY_ADDR)); |
| |
| /* ...and reset PHY */ |
| |
| imxrt_writemii(priv, phyaddr, MII_MCR, MII_MCR_RESET); |
| |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| # endif |
| #endif |
| #if defined (CONFIG_ETH0_PHY_DP83825I) || defined (CONFIG_ETH0_PHY_MULTI) |
| |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| if (imxrt_phy_is(priv, MII_DP83825I_NAME)) |
| { |
| #endif |
| |
| /* Reset PHY */ |
| |
| imxrt_writemii(priv, phyaddr, MII_MCR, MII_MCR_RESET); |
| |
| /* Set RMII mode and Indicate 50MHz clock */ |
| |
| imxrt_writemii(priv, phyaddr, MII_DP83825I_RCSR, |
| MII_DP83825I_RCSC_ELAST_2 | |
| MII_DP83825I_RCSC_RMIICS); |
| |
| imxrt_writemii(priv, phyaddr, MII_ADVERTISE, |
| MII_ADVERTISE_100BASETXFULL | |
| MII_ADVERTISE_100BASETXHALF | |
| MII_ADVERTISE_10BASETXFULL | |
| MII_ADVERTISE_10BASETXHALF | |
| MII_ADVERTISE_CSMA); |
| |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| # endif |
| #endif |
| |
| #if defined(CONFIG_ETH0_PHY_YT8512) || defined(CONFIG_ETH0_PHY_MULTI) |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| if (!imxrt_phy_is(priv, MII_YT8512_NAME)) |
| { |
| # endif |
| /* Reset PHY */ |
| |
| imxrt_writemii(priv, phyaddr, MII_MCR, MII_MCR_RESET); |
| |
| /* Config LEDs */ |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_ADDR_OFFSET, |
| MII_YT8512_LED0); |
| |
| imxrt_readmii(priv, phyaddr, MII_YT8512_DEBUG_DATA, &phydata); |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_ADDR_OFFSET, |
| MII_YT8512_LED0); |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_DATA, 0x331); |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_ADDR_OFFSET, |
| MII_YT8512_LED1); |
| |
| imxrt_readmii(priv, phyaddr, MII_YT8512_DEBUG_DATA, &phydata); |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_ADDR_OFFSET, |
| MII_YT8512_LED1); |
| |
| imxrt_writemii(priv, phyaddr, MII_YT8512_DEBUG_DATA, 0x30); |
| |
| /* Set negotiation */ |
| |
| imxrt_writemii(priv, phyaddr, MII_ADVERTISE, |
| MII_ADVERTISE_100BASETXFULL | |
| MII_ADVERTISE_100BASETXHALF | |
| MII_ADVERTISE_10BASETXFULL | |
| MII_ADVERTISE_10BASETXHALF | |
| MII_ADVERTISE_CSMA); |
| |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| # endif |
| #endif |
| |
| #if !defined(CONFIG_ETH0_PHY_TJA1103) |
| #if defined(CONFIG_ETH0_PHY_MULTI) |
| if (!imxrt_phy_is(priv, MII_TJA1103_NAME)) |
| { |
| #endif |
| /* Start auto negotiation */ |
| |
| ninfo("%s: Start Autonegotiation...\n", BOARD_PHY_NAME); |
| imxrt_writemii(priv, phyaddr, MII_MCR, |
| (MII_MCR_ANRESTART | MII_MCR_ANENABLE)); |
| |
| /* Wait for auto negotiation to complete */ |
| |
| for (retries = 0; retries < LINK_NLOOPS; retries++) |
| { |
| ret = imxrt_readmii(priv, phyaddr, MII_MSR, &phydata); |
| if (ret < 0) |
| { |
| nerr("ERROR: Failed to read %s MII_MSR: %d\n", |
| BOARD_PHY_NAME, ret); |
| return ret; |
| } |
| |
| if (phydata & MII_MSR_ANEGCOMPLETE) |
| { |
| break; |
| } |
| |
| nxsig_usleep(LINK_WAITUS); |
| } |
| |
| if (phydata & MII_MSR_ANEGCOMPLETE) |
| { |
| ninfo("%s: Autonegotiation complete\n", BOARD_PHY_NAME); |
| ninfo("%s: MII_MSR: %04x\n", BOARD_PHY_NAME, phydata); |
| } |
| else |
| { |
| /* TODO: Autonegotiation has right now failed. Maybe the Eth |
| * cable is not connected. PHY chip have mechanisms to |
| * configure link OK. We should leave autconf on, and find a |
| * way to re-configure MCU whenever the link is ready. |
| */ |
| |
| ninfo("%s: Autonegotiation failed [%d] (is cable plugged-in ?)" |
| ", default to 10Mbs mode\n", |
| BOARD_PHY_NAME, retries); |
| |
| /* Stop auto negotiation */ |
| |
| imxrt_writemii(priv, phyaddr, MII_MCR, 0); |
| } |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| # endif |
| #endif |
| } |
| |
| #if !defined(CONFIG_ETH0_PHY_TJA1103) |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| if (!imxrt_phy_is(priv, MII_TJA1103_NAME)) |
| { |
| # endif |
| /* When we get here we have a (negotiated) speed and duplex. This |
| * is also the point we enter if renegotiation is turned off, so have |
| * multiple attempts at reading the status register in case the PHY |
| * isn't awake properly. |
| */ |
| |
| retries = 0; |
| do |
| { |
| phydata = 0xffff; |
| ret = imxrt_readmii(priv, phyaddr, BOARD_PHY_STATUS, &phydata); |
| } |
| while ((ret < 0 || phydata == 0xffff) && ++retries < 3); |
| |
| /* If we didn't successfully read anything and we haven't tried |
| * a physical renegotiation then lets do that |
| */ |
| |
| if (retries >= 3) |
| { |
| if (renogphy == false) |
| { |
| /* Give things one more chance with renegotiation turned on */ |
| |
| return imxrt_initphy(priv, true); |
| } |
| else |
| { |
| /* That didn't end well, just give up */ |
| |
| nerr("ERROR: Failed to read %s BOARD_PHY_STATUS[%02x]: %d\n", |
| BOARD_PHY_NAME, BOARD_PHY_STATUS, ret); |
| return ret; |
| } |
| } |
| |
| ninfo("%s: BOARD_PHY_STATUS: %04x\n", BOARD_PHY_NAME, phydata); |
| # if defined(CONFIG_ETH0_PHY_MULTI) |
| } |
| # endif |
| #endif |
| |
| /* Set up the transmit and receive control registers based on the |
| * configuration and the auto negotiation results. |
| */ |
| |
| #ifdef CONFIG_IMXRT_ENETUSEMII |
| rcr = ENET_RCR_CRCFWD | |
| (CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE) |
| << ENET_RCR_MAX_FL_SHIFT | |
| ENET_RCR_MII_MODE; |
| #else |
| rcr = ENET_RCR_RMII_MODE | ENET_RCR_CRCFWD | |
| (CONFIG_NET_ETH_PKTSIZE + CONFIG_NET_GUARDSIZE) |
| << ENET_RCR_MAX_FL_SHIFT | |
| ENET_RCR_MII_MODE; |
| #endif |
| tcr = 0; |
| |
| imxrt_enet_putreg32(priv, rcr, IMXRT_ENET_RCR_OFFSET); |
| imxrt_enet_putreg32(priv, tcr, IMXRT_ENET_TCR_OFFSET); |
| |
| /* Enable Discard Of Frames With MAC Layer Errors. |
| * Enable Discard Of Frames With Wrong Protocol Checksum. |
| * Bit 1: Enable discard of frames with wrong IPv4 header checksum. |
| */ |
| |
| racc = ENET_RACC_PRODIS | ENET_RACC_LINEDIS | ENET_RACC_IPDIS; |
| imxrt_enet_putreg32(priv, racc, IMXRT_ENET_RACC_OFFSET); |
| |
| /* Setup half or full duplex */ |
| |
| if (BOARD_PHY_ISDUPLEX(phydata)) |
| { |
| /* Full duplex */ |
| |
| ninfo("%s: Full duplex\n", BOARD_PHY_NAME); |
| tcr |= ENET_TCR_FDEN; |
| } |
| else |
| { |
| /* Half duplex */ |
| |
| ninfo("%s: Half duplex\n", BOARD_PHY_NAME); |
| rcr |= ENET_RCR_DRT; |
| } |
| |
| if (BOARD_PHY_10BASET(phydata)) |
| { |
| /* 10 Mbps */ |
| |
| ninfo("%s: 10 Base-T\n", BOARD_PHY_NAME); |
| rcr |= ENET_RCR_RMII_10T; |
| } |
| else if (BOARD_PHY_100BASET(phydata)) |
| { |
| /* 100 Mbps */ |
| |
| ninfo("%s: 100 Base-T\n", BOARD_PHY_NAME); |
| } |
| else |
| { |
| /* This might happen if Autonegotiation did not complete(?) */ |
| |
| nerr("ERROR: Neither 10- nor 100-BaseT reported: PHY STATUS=%04x\n", |
| phydata); |
| return -EIO; |
| } |
| |
| imxrt_enet_putreg32(priv, rcr, IMXRT_ENET_RCR_OFFSET); |
| imxrt_enet_putreg32(priv, tcr, IMXRT_ENET_TCR_OFFSET); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_initbuffers |
| * |
| * Description: |
| * Initialize ENET buffers and descriptors |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_initbuffers(struct imxrt_driver_s *priv) |
| { |
| uintptr_t addr; |
| int i; |
| |
| /* Get an aligned TX descriptor (array) address */ |
| |
| priv->txdesc = &g_desc_pool[0]; |
| |
| /* Get an aligned RX descriptor (array) address */ |
| |
| priv->rxdesc = &g_desc_pool[CONFIG_IMXRT_ENET_NTXBUFFERS]; |
| |
| /* Get the beginning of the first aligned buffer */ |
| |
| addr = (uintptr_t)g_buffer_pool; |
| |
| /* Then fill in the TX descriptors */ |
| |
| for (i = 0; i < CONFIG_IMXRT_ENET_NTXBUFFERS; i++) |
| { |
| priv->txdesc[i].status1 = 0; |
| priv->txdesc[i].length = 0; |
| priv->txdesc[i].data = (uint8_t *)imxrt_swap32((uint32_t)addr); |
| #ifdef CONFIG_IMXRT_ENET_ENHANCEDBD |
| priv->txdesc[i].status2 = TXDESC_IINS | TXDESC_PINS; |
| #endif |
| addr += ALIGNED_BUFSIZE; |
| } |
| |
| /* Then fill in the RX descriptors */ |
| |
| for (i = 0; i < CONFIG_IMXRT_ENET_NRXBUFFERS; i++) |
| { |
| priv->rxdesc[i].status1 = RXDESC_E; |
| priv->rxdesc[i].length = 0; |
| priv->rxdesc[i].data = (uint8_t *)imxrt_swap32((uint32_t)addr); |
| #ifdef CONFIG_IMXRT_ENET_ENHANCEDBD |
| priv->rxdesc[i].bdu = 0; |
| priv->rxdesc[i].status2 = RXDESC_INT; |
| #endif |
| addr += ALIGNED_BUFSIZE; |
| } |
| |
| /* Set the wrap bit in the last descriptors to form a ring */ |
| |
| priv->txdesc[CONFIG_IMXRT_ENET_NTXBUFFERS - 1].status1 |= TXDESC_W; |
| priv->rxdesc[CONFIG_IMXRT_ENET_NRXBUFFERS - 1].status1 |= RXDESC_W; |
| |
| up_clean_dcache((uintptr_t)g_desc_pool, |
| (uintptr_t)g_desc_pool + sizeof(g_desc_pool)); |
| |
| /* We start with RX descriptor 0 and with no TX descriptors in use */ |
| |
| priv->txtail = 0; |
| priv->txhead = 0; |
| priv->rxtail = 0; |
| |
| /* Initialize the packet buffer, which is used when sending */ |
| |
| priv->dev.d_buf = |
| (uint8_t *)imxrt_swap32((uint32_t)priv->txdesc[priv->txhead].data); |
| } |
| |
| /**************************************************************************** |
| * Function: imxrt_reset |
| * |
| * Description: |
| * Put the EMAC in the non-operational, reset state |
| * |
| * Input Parameters: |
| * priv - Reference to the private ENET driver state structure |
| * |
| * Returned Value: |
| * None |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| static void imxrt_reset(struct imxrt_driver_s *priv) |
| { |
| unsigned int i; |
| |
| /* Set the reset bit and clear the enable bit */ |
| |
| imxrt_enet_putreg32(priv, ENET_ECR_RESET, IMXRT_ENET_ECR_OFFSET); |
| |
| /* Wait at least 8 clock cycles */ |
| |
| for (i = 0; i < 10; i++) |
| { |
| asm volatile ("nop"); |
| } |
| } |
| |
| /**************************************************************************** |
| * Public Functions |
| ****************************************************************************/ |
| |
| /**************************************************************************** |
| * Function: imxrt_netinitialize |
| * |
| * Description: |
| * Initialize the Ethernet controller and driver |
| * |
| * Input Parameters: |
| * intf - In the case where there are multiple EMACs, this value |
| * identifies which EMAC is to be initialized. |
| * |
| * Returned Value: |
| * OK on success; Negated errno on failure. |
| * |
| * Assumptions: |
| * |
| ****************************************************************************/ |
| |
| int imxrt_netinitialize(int intf) |
| { |
| struct imxrt_driver_s *priv; |
| #ifdef CONFIG_NET_ETHERNET |
| uint32_t uidl; |
| uint32_t uidml; |
| uint8_t *mac; |
| #endif |
| uint32_t regval; |
| int ret; |
| |
| /* Get the interface structure associated with this interface number. */ |
| |
| DEBUGASSERT(intf < CONFIG_IMXRT_ENET_NETHIFS); |
| priv = &g_enet[intf]; |
| |
| /* Initialize the driver structure */ |
| |
| memset(priv, 0, sizeof(struct imxrt_driver_s)); |
| |
| priv->base = IMXRT_ENETN_BASE; /* Assigne base address */ |
| |
| priv->dev.d_ifup = imxrt_ifup; /* I/F up (new IP address) callback */ |
| priv->dev.d_ifdown = imxrt_ifdown; /* I/F down callback */ |
| priv->dev.d_txavail = imxrt_txavail; /* New TX data callback */ |
| #ifdef CONFIG_NET_MCASTGROUP |
| priv->dev.d_addmac = imxrt_addmac; /* Add multicast MAC address */ |
| priv->dev.d_rmmac = imxrt_rmmac; /* Remove multicast MAC address */ |
| #endif |
| #ifdef CONFIG_NETDEV_IOCTL |
| priv->dev.d_ioctl = imxrt_ioctl; /* Support PHY ioctl() calls */ |
| #endif |
| priv->dev.d_private = g_enet; /* Used to recover private state from dev */ |
| |
| /* Configure ENET1_TX_CLK */ |
| |
| regval = getreg32(IMXRT_ENET_IOMUXC_GPR); |
| regval &= ~GPR_ENET_MASK; |
| regval |= (GPR_ENET_TX_DIR | GPR_ENET_CLK_SEL); |
| putreg32(regval, IMXRT_ENET_IOMUXC_GPR); |
| |
| /* Enable the ENET clock. Clock is on during all modes, |
| * except STOP mode. |
| */ |
| |
| imxrt_clock_enet(); |
| |
| /* Configure all ENET/MII pins */ |
| |
| #if defined(CONFIG_IMXRT_ENET1) |
| imxrt_config_gpio(GPIO_ENET_MDIO); |
| imxrt_config_gpio(GPIO_ENET_MDC); |
| imxrt_config_gpio(GPIO_ENET_RX_EN); |
| imxrt_config_gpio(GPIO_ENET_RX_DATA00); |
| imxrt_config_gpio(GPIO_ENET_RX_DATA01); |
| imxrt_config_gpio(GPIO_ENET_TX_DATA00); |
| imxrt_config_gpio(GPIO_ENET_TX_DATA01); |
| imxrt_config_gpio(GPIO_ENET_TX_CLK); |
| imxrt_config_gpio(GPIO_ENET_TX_EN); |
| # ifdef GPIO_ENET_RX_ER |
| imxrt_config_gpio(GPIO_ENET_RX_ER); |
| # endif |
| #endif |
| |
| #if defined(CONFIG_IMXRT_ENET2) |
| imxrt_config_gpio(GPIO_ENET2_MDIO); |
| imxrt_config_gpio(GPIO_ENET2_MDC); |
| imxrt_config_gpio(GPIO_ENET2_RX_EN); |
| imxrt_config_gpio(GPIO_ENET2_RX_DATA00); |
| imxrt_config_gpio(GPIO_ENET2_RX_DATA01); |
| imxrt_config_gpio(GPIO_ENET2_TX_DATA00); |
| imxrt_config_gpio(GPIO_ENET2_TX_DATA01); |
| imxrt_config_gpio(GPIO_ENET2_TX_CLK); |
| imxrt_config_gpio(GPIO_ENET2_TX_EN); |
| # ifdef GPIO_ENET2_RX_ER |
| imxrt_config_gpio(GPIO_ENET2_RX_ER); |
| # endif |
| #endif |
| |
| /* Attach the Ethernet MAC IEEE 1588 timer interrupt handler */ |
| |
| #if 0 |
| if (irq_attach(IMXRT_IRQ_EMACTMR, imxrt_tmrinterrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| nerr("ERROR: Failed to attach EMACTMR IRQ\n"); |
| return -EAGAIN; |
| } |
| #endif |
| |
| /* Attach the Ethernet interrupt handler */ |
| |
| if (irq_attach(IMXRT_ENET_IRQ, imxrt_enet_interrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| nerr("ERROR: Failed to attach EMACTX IRQ\n"); |
| return -EAGAIN; |
| } |
| |
| #ifdef IMXRT_ENET_IRQ_2 |
| if (irq_attach(IMXRT_ENET_IRQ_2, imxrt_enet_interrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| nerr("ERROR: Failed to attach EMACTX IRQ\n"); |
| return -EAGAIN; |
| } |
| #endif |
| |
| #ifdef IMXRT_ENET_IRQ_3 |
| if (irq_attach(IMXRT_ENET_IRQ_3, imxrt_enet_interrupt, priv)) |
| { |
| /* We could not attach the ISR to the interrupt */ |
| |
| nerr("ERROR: Failed to attach EMACTX IRQ\n"); |
| return -EAGAIN; |
| } |
| #endif |
| |
| #ifdef CONFIG_NET_ETHERNET |
| |
| #ifdef CONFIG_NET_USE_OTP_ETHERNET_MAC |
| |
| /* Boards like the teensy have a unique (official) |
| * MAC address stored in OTP. |
| * TODO: hardcoded mem locations: use proper registers and header file |
| * offsets: 0x620: MAC0, 0x630: MAC1 |
| */ |
| |
| uidl = getreg32(IMXRT_OCOTP_BASE + 0x620); |
| uidml = getreg32(IMXRT_OCOTP_BASE + 0x630); |
| mac = priv->dev.d_mac.ether.ether_addr_octet; |
| |
| mac[0] = (uidml & 0x0000ff00) >> 8; |
| mac[1] = (uidml & 0x000000ff) >> 0; |
| mac[2] = (uidl & 0xff000000) >> 24; |
| mac[3] = (uidl & 0x00ff0000) >> 16; |
| mac[4] = (uidl & 0x0000ff00) >> 8; |
| mac[5] = (uidl & 0x000000ff) >> 0; |
| |
| #else |
| |
| /* Determine a semi-unique MAC address from MCU UID |
| * We use UID Low and Mid Low registers to get 64 bits, from which we keep |
| * 48 bits. We then force unicast and locally administered bits |
| * (b0 and b1, 1st octet) |
| */ |
| |
| uidl = getreg32(IMXRT_OCOTP_UNIQUE_ID_MSB); |
| uidml = getreg32(IMXRT_OCOTP_UNIQUE_ID_LSB); |
| mac = priv->dev.d_mac.ether.ether_addr_octet; |
| |
| uidml |= 0x00000200; |
| uidml &= 0x0000feff; |
| |
| mac[0] = (uidml & 0x0000ff00) >> 8; |
| mac[1] = (uidml & 0x000000ff); |
| mac[2] = (uidl & 0xff000000) >> 24; |
| mac[3] = (uidl & 0x00ff0000) >> 16; |
| mac[4] = (uidl & 0x0000ff00) >> 8; |
| mac[5] = (uidl & 0x000000ff); |
| |
| #endif |
| |
| #endif |
| |
| #ifdef CONFIG_IMXRT_ENET_PHYINIT |
| /* Perform any necessary, one-time, board-specific PHY initialization */ |
| |
| ret = imxrt_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. This usually amounts to resetting |
| * the device and/or calling imxrt_ifdown(). |
| */ |
| |
| imxrt_ifdown(&priv->dev); |
| |
| /* Register the device with the OS so that socket IOCTLs can be performed */ |
| |
| netdev_register(&priv->dev, NET_LL_ETHERNET); |
| |
| UNUSED(ret); |
| return OK; |
| } |
| |
| /**************************************************************************** |
| * Name: arm_netinitialize |
| * |
| * Description: |
| * Initialize the first network interface. If there are more than one |
| * interface in the chip, then board-specific logic will have to provide |
| * this function to determine which, if any, Ethernet controllers should |
| * be initialized. |
| * |
| ****************************************************************************/ |
| |
| #if CONFIG_IMXRT_ENET_NETHIFS == 1 && !defined(CONFIG_NETDEV_LATEINIT) |
| void arm_netinitialize(void) |
| { |
| imxrt_netinitialize(0); |
| } |
| #endif |
| |
| #endif /* CONFIG_IMXRT_ENET */ |