|  | /* | 
|  | * Xilinx Processor System Gigabit Ethernet controller (GEM) driver | 
|  | * | 
|  | * Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG | 
|  | * SPDX-License-Identifier: Apache-2.0 | 
|  | * | 
|  | * Known current limitations / TODOs: | 
|  | * - Only supports 32-bit addresses in buffer descriptors, therefore | 
|  | *   the ZynqMP APU (Cortex-A53 cores) may not be fully supported. | 
|  | * - Hardware timestamps not considered. | 
|  | * - VLAN tags not considered. | 
|  | * - Wake-on-LAN interrupt not supported. | 
|  | * - Send function is not SMP-capable (due to single TX done semaphore). | 
|  | * - Interrupt-driven PHY management not supported - polling only. | 
|  | * - No explicit placement of the DMA memory area(s) in either a | 
|  | *   specific memory section or at a fixed memory location yet. This | 
|  | *   is not an issue as long as the controller is used in conjunction | 
|  | *   with the Cortex-R5 QEMU target or an actual R5 running without the | 
|  | *   MPU enabled. | 
|  | * - No detailed error handling when evaluating the Interrupt Status, | 
|  | *   RX Status and TX Status registers. | 
|  | */ | 
|  |  | 
|  | #include <zephyr.h> | 
|  | #include <device.h> | 
|  | #include <devicetree.h> | 
|  | #include <sys/__assert.h> | 
|  |  | 
|  | #include <net/net_if.h> | 
|  | #include <net/ethernet.h> | 
|  | #include <ethernet/eth_stats.h> | 
|  |  | 
|  | #include "eth_xlnx_gem_priv.h" | 
|  |  | 
|  | #define LOG_MODULE_NAME eth_xlnx_gem | 
|  | #define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL | 
|  | #include <logging/log.h> | 
|  | LOG_MODULE_REGISTER(LOG_MODULE_NAME); | 
|  |  | 
|  | static int  eth_xlnx_gem_dev_init(const struct device *dev); | 
|  | static void eth_xlnx_gem_iface_init(struct net_if *iface); | 
|  | static void eth_xlnx_gem_isr(const struct device *dev); | 
|  | static int  eth_xlnx_gem_send(const struct device *dev, struct net_pkt *pkt); | 
|  | static int  eth_xlnx_gem_start_device(const struct device *dev); | 
|  | static int  eth_xlnx_gem_stop_device(const struct device *dev); | 
|  | static enum ethernet_hw_caps | 
|  | eth_xlnx_gem_get_capabilities(const struct device *dev); | 
|  | #if defined(CONFIG_NET_STATISTICS_ETHERNET) | 
|  | static struct net_stats_eth *eth_xlnx_gem_stats(const struct device *dev); | 
|  | #endif | 
|  |  | 
|  | static void eth_xlnx_gem_reset_hw(const struct device *dev); | 
|  | static void eth_xlnx_gem_configure_clocks(const struct device *dev); | 
|  | static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev); | 
|  | static void eth_xlnx_gem_set_nwcfg_link_speed(const struct device *dev); | 
|  | static void eth_xlnx_gem_set_mac_address(const struct device *dev); | 
|  | static void eth_xlnx_gem_set_initial_dmacr(const struct device *dev); | 
|  | static void eth_xlnx_gem_init_phy(const struct device *dev); | 
|  | static void eth_xlnx_gem_poll_phy(struct k_work *item); | 
|  | static void eth_xlnx_gem_configure_buffers(const struct device *dev); | 
|  | static void eth_xlnx_gem_rx_pending_work(struct k_work *item); | 
|  | static void eth_xlnx_gem_handle_rx_pending(const struct device *dev); | 
|  | static void eth_xlnx_gem_tx_done_work(struct k_work *item); | 
|  | static void eth_xlnx_gem_handle_tx_done(const struct device *dev); | 
|  |  | 
|  | static const struct ethernet_api eth_xlnx_gem_apis = { | 
|  | .iface_api.init   = eth_xlnx_gem_iface_init, | 
|  | .get_capabilities = eth_xlnx_gem_get_capabilities, | 
|  | .send		  = eth_xlnx_gem_send, | 
|  | .start		  = eth_xlnx_gem_start_device, | 
|  | .stop		  = eth_xlnx_gem_stop_device, | 
|  | #if defined(CONFIG_NET_STATISTICS_ETHERNET) | 
|  | .get_stats	  = eth_xlnx_gem_stats, | 
|  | #endif | 
|  | }; | 
|  |  | 
|  | /* | 
|  | * Insert the configuration & run-time data for all GEM instances which | 
|  | * are enabled in the device tree of the current target board. | 
|  | */ | 
|  | DT_INST_FOREACH_STATUS_OKAY(ETH_XLNX_GEM_INITIALIZE) | 
|  |  | 
|  | /** | 
|  | * @brief GEM device initialization function | 
|  | * Initializes the GEM itself, the DMA memory area used by the GEM and, | 
|  | * if enabled, an associated PHY attached to the GEM's MDIO interface. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @retval 0 if the device initialization completed successfully | 
|  | */ | 
|  | static int eth_xlnx_gem_dev_init(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | uint32_t reg_val; | 
|  |  | 
|  | /* Precondition checks using assertions */ | 
|  |  | 
|  | /* Valid PHY address and polling interval, if PHY is to be managed */ | 
|  | if (dev_conf->init_phy) { | 
|  | __ASSERT((dev_conf->phy_mdio_addr_fix >= 0 && | 
|  | dev_conf->phy_mdio_addr_fix <= 32), | 
|  | "%s invalid PHY address %u, must be in range " | 
|  | "1 to 32, or 0 for auto-detection", | 
|  | dev->name, dev_conf->phy_mdio_addr_fix); | 
|  | __ASSERT(dev_conf->phy_poll_interval > 0, | 
|  | "%s has an invalid zero PHY status polling " | 
|  | "interval", dev->name); | 
|  | } | 
|  |  | 
|  | /* Valid max. / nominal link speed value */ | 
|  | __ASSERT((dev_conf->max_link_speed == LINK_10MBIT || | 
|  | dev_conf->max_link_speed == LINK_100MBIT || | 
|  | dev_conf->max_link_speed == LINK_1GBIT), | 
|  | "%s invalid max./nominal link speed value %u", | 
|  | dev->name, (uint32_t)dev_conf->max_link_speed); | 
|  |  | 
|  | /* MDC clock divider validity check, SoC dependent */ | 
|  | #if defined(CONFIG_SOC_XILINX_ZYNQMP) | 
|  | __ASSERT(dev_conf->mdc_divider <= MDC_DIVIDER_48, | 
|  | "%s invalid MDC clock divider value %u, must be in " | 
|  | "range 0 to %u", dev->name, dev_conf->mdc_divider, | 
|  | (uint32_t)MDC_DIVIDER_48); | 
|  | #elif defined(CONFIG_SOC_SERIES_XILINX_ZYNQ7000) | 
|  | __ASSERT(dev_conf->mdc_divider <= MDC_DIVIDER_224, | 
|  | "%s invalid MDC clock divider value %u, must be in " | 
|  | "range 0 to %u", dev->name, dev_conf->mdc_divider, | 
|  | (uint32_t)MDC_DIVIDER_224); | 
|  | #endif | 
|  |  | 
|  | /* AMBA AHB configuration options */ | 
|  | __ASSERT((dev_conf->amba_dbus_width == AMBA_AHB_DBUS_WIDTH_32BIT || | 
|  | dev_conf->amba_dbus_width == AMBA_AHB_DBUS_WIDTH_64BIT || | 
|  | dev_conf->amba_dbus_width == AMBA_AHB_DBUS_WIDTH_128BIT), | 
|  | "%s AMBA AHB bus width configuration is invalid", | 
|  | dev->name); | 
|  | __ASSERT((dev_conf->ahb_burst_length == AHB_BURST_SINGLE || | 
|  | dev_conf->ahb_burst_length == AHB_BURST_INCR4 || | 
|  | dev_conf->ahb_burst_length == AHB_BURST_INCR8 || | 
|  | dev_conf->ahb_burst_length == AHB_BURST_INCR16), | 
|  | "%s AMBA AHB burst length configuration is invalid", | 
|  | dev->name); | 
|  |  | 
|  | /* HW RX buffer size */ | 
|  | __ASSERT((dev_conf->hw_rx_buffer_size == HWRX_BUFFER_SIZE_8KB || | 
|  | dev_conf->hw_rx_buffer_size == HWRX_BUFFER_SIZE_4KB || | 
|  | dev_conf->hw_rx_buffer_size == HWRX_BUFFER_SIZE_2KB || | 
|  | dev_conf->hw_rx_buffer_size == HWRX_BUFFER_SIZE_1KB), | 
|  | "%s hardware RX buffer size configuration is invalid", | 
|  | dev->name); | 
|  |  | 
|  | /* HW RX buffer offset */ | 
|  | __ASSERT(dev_conf->hw_rx_buffer_offset <= 3, | 
|  | "%s hardware RX buffer offset %u is invalid, must be in " | 
|  | "range 0 to 3", dev->name, dev_conf->hw_rx_buffer_offset); | 
|  |  | 
|  | /* | 
|  | * RX & TX buffer sizes | 
|  | * RX Buffer size must be a multiple of 64, as the size of the | 
|  | * corresponding DMA receive buffer in AHB system memory is | 
|  | * expressed as n * 64 bytes in the DMA configuration register. | 
|  | */ | 
|  | __ASSERT(dev_conf->rx_buffer_size % 64 == 0, | 
|  | "%s RX buffer size %u is not a multiple of 64 bytes", | 
|  | dev->name, dev_conf->rx_buffer_size); | 
|  | __ASSERT((dev_conf->rx_buffer_size != 0 && | 
|  | dev_conf->rx_buffer_size <= 16320), | 
|  | "%s RX buffer size %u is invalid, should be >64, " | 
|  | "must be 16320 bytes maximum.", dev->name, | 
|  | dev_conf->rx_buffer_size); | 
|  | __ASSERT((dev_conf->tx_buffer_size != 0 && | 
|  | dev_conf->tx_buffer_size <= 16380), | 
|  | "%s TX buffer size %u is invalid, should be >64, " | 
|  | "must be 16380 bytes maximum.", dev->name, | 
|  | dev_conf->tx_buffer_size); | 
|  |  | 
|  | /* Checksum offloading limitations of the QEMU GEM implementation */ | 
|  | #ifdef CONFIG_QEMU_TARGET | 
|  | __ASSERT(!dev_conf->enable_rx_chksum_offload, | 
|  | "TCP/UDP/IP hardware checksum offloading is not " | 
|  | "supported by the QEMU GEM implementation"); | 
|  | __ASSERT(!dev_conf->enable_tx_chksum_offload, | 
|  | "TCP/UDP/IP hardware checksum offloading is not " | 
|  | "supported by the QEMU GEM implementation"); | 
|  | #endif | 
|  |  | 
|  | /* | 
|  | * Initialization procedure as described in the Zynq-7000 TRM, | 
|  | * chapter 16.3.x. | 
|  | */ | 
|  | eth_xlnx_gem_reset_hw(dev);		/* Chapter 16.3.1 */ | 
|  | eth_xlnx_gem_set_initial_nwcfg(dev);	/* Chapter 16.3.2 */ | 
|  | eth_xlnx_gem_set_mac_address(dev);	/* Chapter 16.3.2 */ | 
|  | eth_xlnx_gem_set_initial_dmacr(dev);	/* Chapter 16.3.2 */ | 
|  |  | 
|  | /* Enable MDIO -> set gem.net_ctrl[mgmt_port_en] */ | 
|  | if (dev_conf->init_phy) { | 
|  | reg_val  = sys_read32(dev_conf->base_addr + | 
|  | ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  | reg_val |= ETH_XLNX_GEM_NWCTRL_MDEN_BIT; | 
|  | sys_write32(reg_val, dev_conf->base_addr + | 
|  | ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  | } | 
|  |  | 
|  | eth_xlnx_gem_configure_clocks(dev);	/* Chapter 16.3.3 */ | 
|  | if (dev_conf->init_phy) { | 
|  | eth_xlnx_gem_init_phy(dev);	/* Chapter 16.3.4 */ | 
|  | } | 
|  | eth_xlnx_gem_configure_buffers(dev);	/* Chapter 16.3.5 */ | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM associated interface initialization function | 
|  | * Initializes the interface associated with a GEM device. | 
|  | * | 
|  | * @param iface Pointer to the associated interface data struct | 
|  | */ | 
|  | static void eth_xlnx_gem_iface_init(struct net_if *iface) | 
|  | { | 
|  | const struct device *dev = net_if_get_device(iface); | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  |  | 
|  | /* Set the initial contents of the current instance's run-time data */ | 
|  | dev_data->iface = iface; | 
|  | net_if_set_link_addr(iface, dev_data->mac_addr, 6, NET_LINK_ETHERNET); | 
|  | ethernet_init(iface); | 
|  | net_if_flag_set(iface, NET_IF_NO_AUTO_START); | 
|  |  | 
|  | /* | 
|  | * Initialize the (delayed) work items for RX pending, TX done | 
|  | * and PHY status polling handlers | 
|  | */ | 
|  | k_work_init(&dev_data->tx_done_work, eth_xlnx_gem_tx_done_work); | 
|  | k_work_init(&dev_data->rx_pend_work, eth_xlnx_gem_rx_pending_work); | 
|  | k_work_init_delayable(&dev_data->phy_poll_delayed_work, | 
|  | eth_xlnx_gem_poll_phy); | 
|  |  | 
|  | /* Initialize TX completion semaphore */ | 
|  | k_sem_init(&dev_data->tx_done_sem, 0, 1); | 
|  |  | 
|  | /* | 
|  | * Initialize semaphores in the RX/TX BD rings which have not | 
|  | * yet been initialized | 
|  | */ | 
|  | k_sem_init(&dev_data->txbd_ring.ring_sem, 1, 1); | 
|  | /* RX BD ring semaphore is not required at the time being */ | 
|  |  | 
|  | /* Initialize the device's interrupt */ | 
|  | dev_conf->config_func(dev); | 
|  |  | 
|  | /* Submit initial PHY status polling delayed work */ | 
|  | k_work_reschedule(&dev_data->phy_poll_delayed_work, K_NO_WAIT); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM interrupt service routine | 
|  | * GEM interrupt service routine. Checks for indications of errors | 
|  | * and either immediately handles RX pending / TX complete notifications | 
|  | * or defers them to the system work queue. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_isr(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_val; | 
|  |  | 
|  | /* Read the interrupt status register */ | 
|  | reg_val = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  |  | 
|  | /* | 
|  | * TODO: handling if one or more error flag(s) are set in the | 
|  | * interrupt status register. -> For now, just log them | 
|  | */ | 
|  | if (reg_val & ETH_XLNX_GEM_IXR_ERRORS_MASK) { | 
|  | LOG_ERR("%s error bit(s) set in Interrupt Status Reg.: 0x%08X", | 
|  | dev->name, reg_val); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check for the following indications by the controller: | 
|  | * reg_val & 0x00000080 -> gem.intr_status bit [7] = Frame TX complete | 
|  | * reg_val & 0x00000002 -> gem.intr_status bit [1] = Frame received | 
|  | * comp. Zynq-7000 TRM, Chapter B.18, p. 1289/1290. | 
|  | * If the respective condition's handling is configured to be deferred | 
|  | * to the work queue thread, submit the corresponding job to the work | 
|  | * queue, otherwise, handle the condition immediately. | 
|  | */ | 
|  | if ((reg_val & ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT) != 0) { | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_work_submit(&dev_data->tx_done_work); | 
|  | } else { | 
|  | eth_xlnx_gem_handle_tx_done(dev); | 
|  | } | 
|  | } | 
|  | if ((reg_val & ETH_XLNX_GEM_IXR_FRAME_RX_BIT) != 0) { | 
|  | sys_write32(ETH_XLNX_GEM_IXR_FRAME_RX_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  | sys_write32(ETH_XLNX_GEM_IXR_FRAME_RX_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  | if (dev_conf->defer_rxp_to_queue) { | 
|  | k_work_submit(&dev_data->rx_pend_work); | 
|  | } else { | 
|  | eth_xlnx_gem_handle_rx_pending(dev); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Clear all interrupt status bits so that the interrupt is de-asserted | 
|  | * by the GEM. -> TXSR/RXSR are read/cleared by either eth_xlnx_gem_- | 
|  | * handle_tx_done or eth_xlnx_gem_handle_rx_pending if those actions | 
|  | * are not deferred to the system's work queue for the current inter- | 
|  | * face. If the latter is the case, those registers will be read/ | 
|  | * cleared whenever the corresponding work item submitted from within | 
|  | * this ISR is being processed. | 
|  | */ | 
|  | sys_write32((0xFFFFFFFF & ~(ETH_XLNX_GEM_IXR_FRAME_RX_BIT | | 
|  | ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT)), | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM data send function | 
|  | * GEM data send function. Blocks until a TX complete notification has been | 
|  | * received & processed. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @param pkt Pointer to the data packet to be sent | 
|  | * @retval -EINVAL in case of invalid parameters, e.g. zero data length | 
|  | * @retval -EIO in case of: | 
|  | *         (1) the attempt to TX data while the device is stopped, | 
|  | *             the interface is down or the link is down, | 
|  | *         (2) the attempt to TX data while no free buffers are available | 
|  | *             in the DMA memory area, | 
|  | *         (3) the transmission completion notification timing out | 
|  | * @retval 0 if the packet was transmitted successfully | 
|  | */ | 
|  | static int eth_xlnx_gem_send(const struct device *dev, struct net_pkt *pkt) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  |  | 
|  | uint16_t tx_data_length; | 
|  | uint16_t tx_data_remaining; | 
|  | void *tx_buffer_offs; | 
|  |  | 
|  | uint8_t bds_reqd; | 
|  | uint8_t curr_bd_idx; | 
|  | uint8_t first_bd_idx; | 
|  |  | 
|  | uint32_t reg_ctrl; | 
|  | uint32_t reg_val; | 
|  | int sem_status; | 
|  |  | 
|  | if (!dev_data->started || dev_data->eff_link_speed == LINK_DOWN || | 
|  | (!net_if_flag_is_set(dev_data->iface, NET_IF_UP))) { | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.tx_dropped++; | 
|  | #endif | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | tx_data_length = tx_data_remaining = net_pkt_get_len(pkt); | 
|  | if (tx_data_length == 0) { | 
|  | LOG_ERR("%s cannot TX, zero packet length", dev->name); | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.errors.tx++; | 
|  | #endif | 
|  | return -EINVAL; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Check if enough buffer descriptors are available for the amount | 
|  | * of data to be transmitted, update the free BD count if this is | 
|  | * the case. Update the 'next to use' BD index in the TX BD ring if | 
|  | * sufficient space is available. If TX done handling, where the BD | 
|  | * ring's data is accessed as well, is performed via the system work | 
|  | * queue, protect against interruptions during the update of the BD | 
|  | * ring's data by taking the ring's semaphore. If TX done handling | 
|  | * is performed within the ISR, protect against interruptions by | 
|  | * disabling the TX done interrupt source. | 
|  | */ | 
|  | bds_reqd = (uint8_t)((tx_data_length + (dev_conf->tx_buffer_size - 1)) / | 
|  | dev_conf->tx_buffer_size); | 
|  |  | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_sem_take(&(dev_data->txbd_ring.ring_sem), K_FOREVER); | 
|  | } else { | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  | } | 
|  |  | 
|  | if (bds_reqd > dev_data->txbd_ring.free_bds) { | 
|  | LOG_ERR("%s cannot TX, packet length %hu requires " | 
|  | "%hhu BDs, current free count = %hhu", | 
|  | dev->name, tx_data_length, bds_reqd, | 
|  | dev_data->txbd_ring.free_bds); | 
|  |  | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_sem_give(&(dev_data->txbd_ring.ring_sem)); | 
|  | } else { | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IER_OFFSET); | 
|  | } | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.tx_dropped++; | 
|  | #endif | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | curr_bd_idx = first_bd_idx = dev_data->txbd_ring.next_to_use; | 
|  | reg_ctrl = (uint32_t)(&dev_data->txbd_ring.first_bd[curr_bd_idx].ctrl); | 
|  |  | 
|  | dev_data->txbd_ring.next_to_use = (first_bd_idx + bds_reqd) % | 
|  | dev_conf->txbd_count; | 
|  | dev_data->txbd_ring.free_bds -= bds_reqd; | 
|  |  | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_sem_give(&(dev_data->txbd_ring.ring_sem)); | 
|  | } else { | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IER_OFFSET); | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Scatter the contents of the network packet's buffer to | 
|  | * one or more DMA buffers. | 
|  | */ | 
|  | net_pkt_cursor_init(pkt); | 
|  | do { | 
|  | /* Calculate the base pointer of the target TX buffer */ | 
|  | tx_buffer_offs = (void *)(dev_data->first_tx_buffer + | 
|  | (dev_conf->tx_buffer_size * curr_bd_idx)); | 
|  |  | 
|  | /* Copy packet data to DMA buffer */ | 
|  | net_pkt_read(pkt, (void *)tx_buffer_offs, | 
|  | (tx_data_remaining < dev_conf->tx_buffer_size) ? | 
|  | tx_data_remaining : dev_conf->tx_buffer_size); | 
|  |  | 
|  | /* Update current BD's control word */ | 
|  | reg_val = sys_read32(reg_ctrl) & (ETH_XLNX_GEM_TXBD_WRAP_BIT | | 
|  | ETH_XLNX_GEM_TXBD_USED_BIT); | 
|  | reg_val |= (tx_data_remaining < dev_conf->tx_buffer_size) ? | 
|  | tx_data_remaining : dev_conf->tx_buffer_size; | 
|  | sys_write32(reg_val, reg_ctrl); | 
|  |  | 
|  | if (tx_data_remaining > dev_conf->tx_buffer_size) { | 
|  | /* Switch to next BD */ | 
|  | curr_bd_idx = (curr_bd_idx + 1) % dev_conf->txbd_count; | 
|  | reg_ctrl = (uint32_t)(&dev_data->txbd_ring.first_bd[curr_bd_idx].ctrl); | 
|  | } | 
|  |  | 
|  | tx_data_remaining -= (tx_data_remaining < dev_conf->tx_buffer_size) ? | 
|  | tx_data_remaining : dev_conf->tx_buffer_size; | 
|  | } while (tx_data_remaining > 0); | 
|  |  | 
|  | /* Set the 'last' bit in the current BD's control word */ | 
|  | reg_val |= ETH_XLNX_GEM_TXBD_LAST_BIT; | 
|  |  | 
|  | /* | 
|  | * Clear the 'used' bits of all BDs involved in the current | 
|  | * transmission. In accordance with chapter 16.3.8 of the | 
|  | * Zynq-7000 TRM, the 'used' bits shall be cleared in reverse | 
|  | * order, so that the 'used' bit of the first BD is cleared | 
|  | * last just before the transmission is started. | 
|  | */ | 
|  | reg_val &= ~ETH_XLNX_GEM_TXBD_USED_BIT; | 
|  | sys_write32(reg_val, reg_ctrl); | 
|  |  | 
|  | while (curr_bd_idx != first_bd_idx) { | 
|  | curr_bd_idx = (curr_bd_idx != 0) ? (curr_bd_idx - 1) : | 
|  | (dev_conf->txbd_count - 1); | 
|  | reg_ctrl = (uint32_t)(&dev_data->txbd_ring.first_bd[curr_bd_idx].ctrl); | 
|  | reg_val = sys_read32(reg_ctrl); | 
|  | reg_val &= ~ETH_XLNX_GEM_TXBD_USED_BIT; | 
|  | sys_write32(reg_val, reg_ctrl); | 
|  | } | 
|  |  | 
|  | /* Set the start TX bit in the gem.net_ctrl register */ | 
|  | reg_val  = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  | reg_val |= ETH_XLNX_GEM_NWCTRL_STARTTX_BIT; | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  |  | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.bytes.sent += tx_data_length; | 
|  | dev_data->stats.pkts.tx++; | 
|  | #endif | 
|  |  | 
|  | /* Block until TX has completed */ | 
|  | sem_status = k_sem_take(&dev_data->tx_done_sem, K_MSEC(100)); | 
|  | if (sem_status < 0) { | 
|  | LOG_ERR("%s TX confirmation timed out", dev->name); | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.tx_timeout_count++; | 
|  | #endif | 
|  | return -EIO; | 
|  | } | 
|  |  | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM device start function | 
|  | * GEM device start function. Clears all status registers and any | 
|  | * pending interrupts, enables RX and TX, enables interrupts. If | 
|  | * no PHY is managed by the current driver instance, this function | 
|  | * also declares the physical link up at the configured nominal | 
|  | * link speed. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @retval    0 upon successful completion | 
|  | */ | 
|  | static int eth_xlnx_gem_start_device(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_val; | 
|  |  | 
|  | if (dev_data->started) { | 
|  | return 0; | 
|  | } | 
|  | dev_data->started = true; | 
|  |  | 
|  | /* Disable & clear all the MAC interrupts */ | 
|  | sys_write32(ETH_XLNX_GEM_IXR_ALL_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  | sys_write32(ETH_XLNX_GEM_IXR_ALL_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  |  | 
|  | /* Clear RX & TX status registers */ | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_TXSR_OFFSET); | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_RXSR_OFFSET); | 
|  |  | 
|  | /* RX and TX enable */ | 
|  | reg_val  = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  | reg_val |= (ETH_XLNX_GEM_NWCTRL_RXEN_BIT | ETH_XLNX_GEM_NWCTRL_TXEN_BIT); | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  |  | 
|  | /* Enable all the MAC interrupts */ | 
|  | sys_write32(ETH_XLNX_GEM_IXR_ALL_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IER_OFFSET); | 
|  |  | 
|  | /* Submit the delayed work for polling the link state */ | 
|  | if (k_work_delayable_remaining_get(&dev_data->phy_poll_delayed_work) == 0) { | 
|  | k_work_reschedule(&dev_data->phy_poll_delayed_work, K_NO_WAIT); | 
|  | } | 
|  |  | 
|  | LOG_DBG("%s started", dev->name); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM device stop function | 
|  | * GEM device stop function. Disables all interrupts, disables | 
|  | * RX and TX, clears all status registers. If no PHY is managed | 
|  | * by the current driver instance, this function also declares | 
|  | * the physical link down. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @retval    0 upon successful completion | 
|  | */ | 
|  | static int eth_xlnx_gem_stop_device(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_val; | 
|  |  | 
|  | if (!dev_data->started) { | 
|  | return 0; | 
|  | } | 
|  | dev_data->started = false; | 
|  |  | 
|  | /* Cancel the delayed work that polls the link state */ | 
|  | if (k_work_delayable_remaining_get(&dev_data->phy_poll_delayed_work) != 0) { | 
|  | k_work_cancel_delayable(&dev_data->phy_poll_delayed_work); | 
|  | } | 
|  |  | 
|  | /* RX and TX disable */ | 
|  | reg_val  = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  | reg_val &= (~(ETH_XLNX_GEM_NWCTRL_RXEN_BIT | ETH_XLNX_GEM_NWCTRL_TXEN_BIT)); | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  |  | 
|  | /* Disable & clear all the MAC interrupts */ | 
|  | sys_write32(ETH_XLNX_GEM_IXR_ALL_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  | sys_write32(ETH_XLNX_GEM_IXR_ALL_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_ISR_OFFSET); | 
|  |  | 
|  | /* Clear RX & TX status registers */ | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_TXSR_OFFSET); | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_RXSR_OFFSET); | 
|  |  | 
|  | LOG_DBG("%s stopped", dev->name); | 
|  | return 0; | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM capability request function | 
|  | * Returns the capabilities of the GEM controller as an enumeration. | 
|  | * All of the data returned is derived from the device configuration | 
|  | * of the current GEM device instance. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @return Enumeration containing the current GEM device's capabilities | 
|  | */ | 
|  | static enum ethernet_hw_caps eth_xlnx_gem_get_capabilities( | 
|  | const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | enum ethernet_hw_caps caps = (enum ethernet_hw_caps)0; | 
|  |  | 
|  | if (dev_conf->max_link_speed == LINK_1GBIT) { | 
|  | if (dev_conf->phy_advertise_lower) { | 
|  | caps |= (ETHERNET_LINK_1000BASE_T | | 
|  | ETHERNET_LINK_100BASE_T | | 
|  | ETHERNET_LINK_10BASE_T); | 
|  | } else { | 
|  | caps |= ETHERNET_LINK_1000BASE_T; | 
|  | } | 
|  | } else if (dev_conf->max_link_speed == LINK_100MBIT) { | 
|  | if (dev_conf->phy_advertise_lower) { | 
|  | caps |= (ETHERNET_LINK_100BASE_T | | 
|  | ETHERNET_LINK_10BASE_T); | 
|  | } else { | 
|  | caps |= ETHERNET_LINK_100BASE_T; | 
|  | } | 
|  | } else { | 
|  | caps |= ETHERNET_LINK_10BASE_T; | 
|  | } | 
|  |  | 
|  | if (dev_conf->enable_rx_chksum_offload) { | 
|  | caps |= ETHERNET_HW_RX_CHKSUM_OFFLOAD; | 
|  | } | 
|  |  | 
|  | if (dev_conf->enable_tx_chksum_offload) { | 
|  | caps |= ETHERNET_HW_TX_CHKSUM_OFFLOAD; | 
|  | } | 
|  |  | 
|  | if (dev_conf->enable_fdx) { | 
|  | caps |= ETHERNET_DUPLEX_SET; | 
|  | } | 
|  |  | 
|  | if (dev_conf->copy_all_frames) { | 
|  | caps |= ETHERNET_PROMISC_MODE; | 
|  | } | 
|  |  | 
|  | return caps; | 
|  | } | 
|  |  | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | /** | 
|  | * @brief GEM statistics data request function | 
|  | * Returns a pointer to the statistics data of the current GEM controller. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | * @return Pointer to the current GEM device's statistics data | 
|  | */ | 
|  | static struct net_stats_eth *eth_xlnx_gem_stats(const struct device *dev) | 
|  | { | 
|  | return &(DEV_DATA(dev)->stats); | 
|  | } | 
|  | #endif | 
|  |  | 
|  | /** | 
|  | * @brief GEM Hardware reset function | 
|  | * Resets the current GEM device. Called from within the device | 
|  | * initialization function. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_reset_hw(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  |  | 
|  | /* | 
|  | * Controller reset sequence as described in the Zynq-7000 TRM, | 
|  | * chapter 16.3.1. | 
|  | */ | 
|  |  | 
|  | /* Clear the NWCTRL register */ | 
|  | sys_write32(0x00000000, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  |  | 
|  | /* Clear the statistics counters */ | 
|  | sys_write32(ETH_XLNX_GEM_STATCLR_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); | 
|  |  | 
|  | /* Clear the RX/TX status registers */ | 
|  | sys_write32(ETH_XLNX_GEM_TXSRCLR_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_TXSR_OFFSET); | 
|  | sys_write32(ETH_XLNX_GEM_RXSRCLR_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_RXSR_OFFSET); | 
|  |  | 
|  | /* Disable all interrupts */ | 
|  | sys_write32(ETH_XLNX_GEM_IDRCLR_MASK, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IDR_OFFSET); | 
|  |  | 
|  | /* Clear the buffer queues */ | 
|  | sys_write32(0x00000000, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_RXQBASE_OFFSET); | 
|  | sys_write32(0x00000000, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_TXQBASE_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM clock configuration function | 
|  | * Calculates the pre-scalers for the TX clock to match the current | 
|  | * (if an associated PHY is managed) or nominal link speed. Called | 
|  | * from within the device initialization function. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_configure_clocks(const struct device *dev) | 
|  | { | 
|  | /* | 
|  | * Clock source configuration for the respective GEM as described | 
|  | * in the Zynq-7000 TRM, chapter 16.3.3, is not tackled here. This | 
|  | * is performed by the PS7Init code. Only the DIVISOR and DIVISOR1 | 
|  | * values for the respective GEM's TX clock are calculated here. | 
|  | */ | 
|  |  | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  |  | 
|  | uint32_t div0; | 
|  | uint32_t div1; | 
|  | uint32_t target = 2500000; /* default prevents 'may be uninitialized' warning */ | 
|  | uint32_t tmp; | 
|  | uint32_t clk_ctrl_reg; | 
|  |  | 
|  | if ((!dev_conf->init_phy) || dev_data->eff_link_speed == LINK_DOWN) { | 
|  | /* | 
|  | * Run-time data indicates 'link down' or PHY management | 
|  | * is disabled for the current device -> this indicates the | 
|  | * initial device initialization. Once the PHY status polling | 
|  | * delayed work handler has picked up the result of the auto- | 
|  | * negotiation (if enabled), this if-statement will evaluate | 
|  | * to false. | 
|  | */ | 
|  | if (dev_conf->max_link_speed == LINK_10MBIT) { | 
|  | target = 2500000;   /* Target frequency: 2.5 MHz */ | 
|  | } else if (dev_conf->max_link_speed == LINK_100MBIT) { | 
|  | target = 25000000;  /* Target frequency: 25 MHz */ | 
|  | } else if (dev_conf->max_link_speed == LINK_1GBIT) { | 
|  | target = 125000000; /* Target frequency: 125 MHz */ | 
|  | } | 
|  | } else if (dev_data->eff_link_speed != LINK_DOWN) { | 
|  | /* | 
|  | * Use the effective link speed instead of the maximum/nominal | 
|  | * link speed for clock configuration. | 
|  | */ | 
|  | if (dev_data->eff_link_speed == LINK_10MBIT) { | 
|  | target = 2500000;   /* Target frequency: 2.5 MHz */ | 
|  | } else if (dev_data->eff_link_speed == LINK_100MBIT) { | 
|  | target = 25000000;  /* Target frequency: 25 MHz */ | 
|  | } else if (dev_data->eff_link_speed == LINK_1GBIT) { | 
|  | target = 125000000; /* Target frequency: 125 MHz */ | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Caclculate the divisors for the target frequency. | 
|  | * The frequency of the PLL to which the divisors shall be applied are | 
|  | * provided in the respective GEM's device tree data. | 
|  | */ | 
|  | for (div0 = 1; div0 < 64; div0++) { | 
|  | for (div1 = 1; div1 < 64; div1++) { | 
|  | tmp = ((dev_conf->pll_clock_frequency / div0) / div1); | 
|  | if (tmp >= (target - 10) && tmp <= (target + 10)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (tmp >= (target - 10) && tmp <= (target + 10)) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | #if defined(CONFIG_SOC_XILINX_ZYNQMP) | 
|  | /* | 
|  | * ZynqMP register crl_apb.GEMx_REF_CTRL: | 
|  | * RX_CLKACT bit [26] | 
|  | * CLKACT bit [25] | 
|  | * div0 bits [13..8], div1 bits [21..16] | 
|  | * Unlock CRL_APB write access if the write protect bit | 
|  | * is currently set, restore it afterwards. | 
|  | */ | 
|  | clk_ctrl_reg  = sys_read32(dev_conf->clk_ctrl_reg_address); | 
|  | clk_ctrl_reg &= ~((ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR_MASK << | 
|  | ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR0_SHIFT) | | 
|  | (ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR_MASK << | 
|  | ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR1_SHIFT)); | 
|  | clk_ctrl_reg |=	((div0 & ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR_MASK) << | 
|  | ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR0_SHIFT) | | 
|  | ((div1 & ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR_MASK) << | 
|  | ETH_XLNX_CRL_APB_GEMX_REF_CTRL_DIVISOR1_SHIFT); | 
|  | clk_ctrl_reg |=	ETH_XLNX_CRL_APB_GEMX_REF_CTRL_RX_CLKACT_BIT | | 
|  | ETH_XLNX_CRL_APB_GEMX_REF_CTRL_CLKACT_BIT; | 
|  |  | 
|  | /* | 
|  | * Unlock CRL_APB write access if the write protect bit | 
|  | * is currently set, restore it afterwards. | 
|  | */ | 
|  | tmp = sys_read32(ETH_XLNX_CRL_APB_WPROT_REGISTER_ADDRESS); | 
|  | if ((tmp & ETH_XLNX_CRL_APB_WPROT_BIT) > 0) { | 
|  | sys_write32((tmp & ~ETH_XLNX_CRL_APB_WPROT_BIT), | 
|  | ETH_XLNX_CRL_APB_WPROT_REGISTER_ADDRESS); | 
|  | } | 
|  | sys_write32(clk_ctrl_reg, dev_conf->clk_ctrl_reg_address); | 
|  | if ((tmp & ETH_XLNX_CRL_APB_WPROT_BIT) > 0) { | 
|  | sys_write32(tmp, ETH_XLNX_CRL_APB_WPROT_REGISTER_ADDRESS); | 
|  | } | 
|  | # elif defined(CONFIG_SOC_SERIES_XILINX_ZYNQ7000) | 
|  | clk_ctrl_reg  = sys_read32(dev_conf->clk_ctrl_reg_address); | 
|  | clk_ctrl_reg &= ~((ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK << | 
|  | ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) | | 
|  | (ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK << | 
|  | ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT)); | 
|  | clk_ctrl_reg |= ((div0 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) << | 
|  | ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR0_SHIFT) | | 
|  | ((div1 & ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR_MASK) << | 
|  | ETH_XLNX_SLCR_GEMX_CLK_CTRL_DIVISOR1_SHIFT); | 
|  |  | 
|  | /* | 
|  | * SLCR must be unlocked prior to and locked after writing to | 
|  | * the clock configuration register. | 
|  | */ | 
|  | sys_write32(ETH_XLNX_SLCR_UNLOCK_KEY, | 
|  | ETH_XLNX_SLCR_UNLOCK_REGISTER_ADDRESS); | 
|  | sys_write32(clk_ctrl_reg, dev_conf->clk_ctrl_reg_address); | 
|  | sys_write32(ETH_XLNX_SLCR_LOCK_KEY, | 
|  | ETH_XLNX_SLCR_LOCK_REGISTER_ADDRESS); | 
|  | #endif /* CONFIG_SOC_XILINX_ZYNQMP / CONFIG_SOC_SERIES_XILINX_ZYNQ7000 */ | 
|  |  | 
|  | LOG_DBG("%s set clock dividers div0/1 %u/%u for target " | 
|  | "frequency %u Hz", dev->name, div0, div1, target); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM initial Network Configuration Register setup function | 
|  | * Writes the contents of the current GEM device's Network Configuration | 
|  | * Register (NWCFG / gem.net_cfg). Called from within the device | 
|  | * initialization function. Implementation differs depending on whether | 
|  | * the current target is a Zynq-7000 or a ZynqMP. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | uint32_t reg_val = 0; | 
|  |  | 
|  | if (dev_conf->ignore_ipg_rxer) { | 
|  | /* [30]     ignore IPG rx_er */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_IGNIPGRXERR_BIT; | 
|  | } | 
|  | if (dev_conf->disable_reject_nsp) { | 
|  | /* [29]     disable rejection of non-standard preamble */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_BADPREAMBEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_ipg_stretch) { | 
|  | /* [28]     enable IPG stretch */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_IPG_STRETCH_BIT; | 
|  | } | 
|  | if (dev_conf->enable_sgmii_mode) { | 
|  | /* [27]     SGMII mode enable */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_SGMIIEN_BIT; | 
|  | } | 
|  | if (dev_conf->disable_reject_fcs_crc_errors) { | 
|  | /* [26]     disable rejection of FCS/CRC errors */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_FCSIGNORE_BIT; | 
|  | } | 
|  | if (dev_conf->enable_rx_halfdup_while_tx) { | 
|  | /* [25]     RX half duplex while TX enable */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_HDRXEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_rx_chksum_offload) { | 
|  | /* [24]     enable RX IP/TCP/UDP checksum offload */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_RXCHKSUMEN_BIT; | 
|  | } | 
|  | if (dev_conf->disable_pause_copy) { | 
|  | /* [23]     Do not copy pause Frames to memory */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_PAUSECOPYDI_BIT; | 
|  | } | 
|  | /* [22..21] Data bus width */ | 
|  | reg_val |= (((uint32_t)(dev_conf->amba_dbus_width) & | 
|  | ETH_XLNX_GEM_NWCFG_DBUSW_MASK) << | 
|  | ETH_XLNX_GEM_NWCFG_DBUSW_SHIFT); | 
|  | /* [20..18] MDC clock divider */ | 
|  | reg_val |= (((uint32_t)dev_conf->mdc_divider & | 
|  | ETH_XLNX_GEM_NWCFG_MDC_MASK) << | 
|  | ETH_XLNX_GEM_NWCFG_MDC_SHIFT); | 
|  | if (dev_conf->discard_rx_fcs) { | 
|  | /* [17]     Discard FCS from received frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_FCSREM_BIT; | 
|  | } | 
|  | if (dev_conf->discard_rx_length_errors) { | 
|  | /* [16]     RX length error discard */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_LENGTHERRDSCRD_BIT; | 
|  | } | 
|  | /* [15..14] RX buffer offset */ | 
|  | reg_val |= (((uint32_t)dev_conf->hw_rx_buffer_offset & | 
|  | ETH_XLNX_GEM_NWCFG_RXOFFS_MASK) << | 
|  | ETH_XLNX_GEM_NWCFG_RXOFFS_SHIFT); | 
|  | if (dev_conf->enable_pause) { | 
|  | /* [13]     Enable pause TX */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_PAUSEEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_tbi) { | 
|  | /* [11]     enable TBI instead of GMII/MII */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_TBIINSTEAD_BIT; | 
|  | } | 
|  | if (dev_conf->ext_addr_match) { | 
|  | /* [09]     External address match enable */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_EXTADDRMATCHEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_1536_frames) { | 
|  | /* [08]     Enable 1536 byte frames reception */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_1536RXEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_ucast_hash) { | 
|  | /* [07]     Receive unicast hash frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_UCASTHASHEN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_mcast_hash) { | 
|  | /* [06]     Receive multicast hash frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_MCASTHASHEN_BIT; | 
|  | } | 
|  | if (dev_conf->disable_bcast) { | 
|  | /* [05]     Do not receive broadcast frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_BCASTDIS_BIT; | 
|  | } | 
|  | if (dev_conf->copy_all_frames) { | 
|  | /* [04]     Copy all frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_COPYALLEN_BIT; | 
|  | } | 
|  | if (dev_conf->discard_non_vlan) { | 
|  | /* [02]     Receive only VLAN frames */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_NVLANDISC_BIT; | 
|  | } | 
|  | if (dev_conf->enable_fdx) { | 
|  | /* [01]     enable Full duplex */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_FDEN_BIT; | 
|  | } | 
|  | if (dev_conf->max_link_speed == LINK_100MBIT) { | 
|  | /* [00]     10 or 100 Mbps */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_100_BIT; | 
|  | } else if (dev_conf->max_link_speed == LINK_1GBIT) { | 
|  | /* [10]     Gigabit mode enable */ | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_1000_BIT; | 
|  | } | 
|  | /* | 
|  | * No else-branch for 10Mbit/s mode: | 
|  | * in 10 Mbit/s mode, both bits [00] and [10] remain 0 | 
|  | */ | 
|  |  | 
|  | /* Write the assembled register contents to gem.net_cfg */ | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM Network Configuration Register link speed update function | 
|  | * Updates only the link speed-related bits of the Network Configuration | 
|  | * register. This is called from within #eth_xlnx_gem_poll_phy. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_set_nwcfg_link_speed(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_val; | 
|  |  | 
|  | /* | 
|  | * Read the current gem.net_cfg register contents and mask out | 
|  | * the link speed-related bits | 
|  | */ | 
|  | reg_val  = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); | 
|  | reg_val &= ~(ETH_XLNX_GEM_NWCFG_1000_BIT | ETH_XLNX_GEM_NWCFG_100_BIT); | 
|  |  | 
|  | /* No bits to set for 10 Mbps. 100 Mbps and 1 Gbps set one bit each. */ | 
|  | if (dev_data->eff_link_speed == LINK_100MBIT) { | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_100_BIT; | 
|  | } else if (dev_data->eff_link_speed == LINK_1GBIT) { | 
|  | reg_val |= ETH_XLNX_GEM_NWCFG_1000_BIT; | 
|  | } | 
|  |  | 
|  | /* Write the assembled register contents to gem.net_cfg */ | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM MAC address setup function | 
|  | * Acquires the MAC address to be assigned to the current GEM device | 
|  | * from the device configuration data which in turn acquires it from | 
|  | * the device tree data, then writes it to the gem.spec_addr1_bot/LADDR1L | 
|  | * and gem.spec_addr1_top/LADDR1H registers. Called from within the device | 
|  | * initialization function. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_set_mac_address(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t regval_top; | 
|  | uint32_t regval_bot; | 
|  |  | 
|  | regval_bot  = (dev_data->mac_addr[0] & 0xFF); | 
|  | regval_bot |= (dev_data->mac_addr[1] & 0xFF) << 8; | 
|  | regval_bot |= (dev_data->mac_addr[2] & 0xFF) << 16; | 
|  | regval_bot |= (dev_data->mac_addr[3] & 0xFF) << 24; | 
|  |  | 
|  | regval_top  = (dev_data->mac_addr[4] & 0xFF); | 
|  | regval_top |= (dev_data->mac_addr[5] & 0xFF) << 8; | 
|  |  | 
|  | sys_write32(regval_bot, dev_conf->base_addr + ETH_XLNX_GEM_LADDR1L_OFFSET); | 
|  | sys_write32(regval_top, dev_conf->base_addr + ETH_XLNX_GEM_LADDR1H_OFFSET); | 
|  |  | 
|  | LOG_DBG("%s MAC %02X:%02X:%02X:%02X:%02X:%02X", | 
|  | dev->name, | 
|  | dev_data->mac_addr[0], | 
|  | dev_data->mac_addr[1], | 
|  | dev_data->mac_addr[2], | 
|  | dev_data->mac_addr[3], | 
|  | dev_data->mac_addr[4], | 
|  | dev_data->mac_addr[5]); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM initial DMA Control Register setup function | 
|  | * Writes the contents of the current GEM device's DMA Control Register | 
|  | * (DMACR / gem.dma_cfg). Called from within the device initialization | 
|  | * function. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_set_initial_dmacr(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | uint32_t reg_val = 0; | 
|  |  | 
|  | /* | 
|  | * gem.dma_cfg register bit (field) definitions: | 
|  | * comp. Zynq-7000 TRM, p. 1278 ff. | 
|  | */ | 
|  |  | 
|  | if (dev_conf->disc_rx_ahb_unavail) { | 
|  | /* [24] Discard RX packet when AHB unavailable */ | 
|  | reg_val |= ETH_XLNX_GEM_DMACR_DISCNOAHB_BIT; | 
|  | } | 
|  | /* | 
|  | * [23..16] DMA RX buffer size in AHB system memory | 
|  | *    e.g.: 0x02 = 128, 0x18 = 1536, 0xA0 = 10240 | 
|  | */ | 
|  | reg_val |= (((dev_conf->rx_buffer_size / 64) & | 
|  | ETH_XLNX_GEM_DMACR_RX_BUF_MASK) << | 
|  | ETH_XLNX_GEM_DMACR_RX_BUF_SHIFT); | 
|  | if (dev_conf->enable_tx_chksum_offload) { | 
|  | /* [11] TX TCP/UDP/IP checksum offload to GEM */ | 
|  | reg_val |= ETH_XLNX_GEM_DMACR_TCP_CHKSUM_BIT; | 
|  | } | 
|  | if (dev_conf->tx_buffer_size_full) { | 
|  | /* [10] TX buffer memory size select */ | 
|  | reg_val |= ETH_XLNX_GEM_DMACR_TX_SIZE_BIT; | 
|  | } | 
|  | /* | 
|  | * [09..08] RX packet buffer memory size select | 
|  | *          0 = 1kB, 1 = 2kB, 2 = 4kB, 3 = 8kB | 
|  | */ | 
|  | reg_val |= (((uint32_t)dev_conf->hw_rx_buffer_size << | 
|  | ETH_XLNX_GEM_DMACR_RX_SIZE_SHIFT) & | 
|  | ETH_XLNX_GEM_DMACR_RX_SIZE_MASK); | 
|  | if (dev_conf->enable_ahb_packet_endian_swap) { | 
|  | /* [07] AHB packet data endian swap enable */ | 
|  | reg_val |= ETH_XLNX_GEM_DMACR_ENDIAN_BIT; | 
|  | } | 
|  | if (dev_conf->enable_ahb_md_endian_swap) { | 
|  | /* [06] AHB mgmt descriptor endian swap enable */ | 
|  | reg_val |= ETH_XLNX_GEM_DMACR_DESCR_ENDIAN_BIT; | 
|  | } | 
|  | /* | 
|  | * [04..00] AHB fixed burst length for DMA ops. | 
|  | *          00001 = single AHB bursts, | 
|  | *          001xx = attempt to use INCR4  bursts, | 
|  | *          01xxx = attempt to use INCR8  bursts, | 
|  | *          1xxxx = attempt to use INCR16 bursts | 
|  | */ | 
|  | reg_val |= ((uint32_t)dev_conf->ahb_burst_length & | 
|  | ETH_XLNX_GEM_DMACR_AHB_BURST_LENGTH_MASK); | 
|  |  | 
|  | /* Write the assembled register contents */ | 
|  | sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_DMACR_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM associated PHY detection and setup function | 
|  | * If the current GEM device shall manage an associated PHY, its detection | 
|  | * and configuration is performed from within this function. Called from | 
|  | * within the device initialization function. This function refers to | 
|  | * functionality implemented in the phy_xlnx_gem module. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_init_phy(const struct device *dev) | 
|  | { | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | int detect_rc; | 
|  |  | 
|  | LOG_DBG("%s attempting to initialize associated PHY", dev->name); | 
|  |  | 
|  | /* | 
|  | * The phy_xlnx_gem_detect function checks if a valid PHY | 
|  | * ID is returned when reading the corresponding high / low | 
|  | * ID registers for all valid MDIO addresses. If a compatible | 
|  | * PHY is detected, the function writes a pointer to the | 
|  | * vendor-specific implementations of the PHY management | 
|  | * functions to the run-time device data struct, along with | 
|  | * the ID and the MDIO address of the detected PHY (dev_data-> | 
|  | * phy_id, dev_data->phy_addr, dev_data->phy_access_api). | 
|  | */ | 
|  | detect_rc = phy_xlnx_gem_detect(dev); | 
|  |  | 
|  | if (detect_rc == 0 && dev_data->phy_id != 0x00000000 && | 
|  | dev_data->phy_id != 0xFFFFFFFF && | 
|  | dev_data->phy_access_api != NULL) { | 
|  | /* A compatible PHY was detected -> reset & configure it */ | 
|  | dev_data->phy_access_api->phy_reset_func(dev); | 
|  | dev_data->phy_access_api->phy_configure_func(dev); | 
|  | } else { | 
|  | LOG_WRN("%s no compatible PHY detected", dev->name); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM associated PHY status polling function | 
|  | * This handler of a delayed work item is called from the context of | 
|  | * the system work queue. It is always scheduled at least once during the | 
|  | * interface initialization. If the current driver instance manages a | 
|  | * PHY, the delayed work item will be re-scheduled in order to continuously | 
|  | * monitor the link state and speed while the device is active. Link state | 
|  | * and link speed changes are polled, which may result in the link state | 
|  | * change being propagated (carrier on/off) and / or the TX clock being | 
|  | * reconfigured to match the current link speed. If PHY management is dis- | 
|  | * abled for the current driver instance or no compatible PHY was detected, | 
|  | * the work item will not be re-scheduled and default link speed and link | 
|  | * state values are applied. This function refers to functionality imple- | 
|  | * mented in the phy_xlnx_gem module. | 
|  | * | 
|  | * @param work Pointer to the delayed work item which facilitates | 
|  | *             access to the current device's configuration data | 
|  | */ | 
|  | static void eth_xlnx_gem_poll_phy(struct k_work *work) | 
|  | { | 
|  | struct k_work_delayable *dwork = k_work_delayable_from_work(work); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = CONTAINER_OF(dwork, | 
|  | struct eth_xlnx_gem_dev_data, phy_poll_delayed_work); | 
|  | const struct device *dev = net_if_get_device(dev_data->iface); | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  |  | 
|  | uint16_t phy_status; | 
|  | uint8_t link_status; | 
|  |  | 
|  | if (dev_data->phy_access_api != NULL) { | 
|  | /* A supported PHY is managed by the driver */ | 
|  | phy_status = dev_data->phy_access_api->phy_poll_status_change_func(dev); | 
|  |  | 
|  | if ((phy_status & ( | 
|  | PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED | | 
|  | PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED | | 
|  | PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE)) != 0) { | 
|  |  | 
|  | /* | 
|  | * Get the PHY's link status. Handling a 'link down' | 
|  | * event the simplest possible case. | 
|  | */ | 
|  | link_status = dev_data->phy_access_api->phy_poll_link_status_func(dev); | 
|  |  | 
|  | if (link_status == 0) { | 
|  | /* | 
|  | * Link is down -> propagate to the Ethernet | 
|  | * layer that the link has gone down. | 
|  | */ | 
|  | dev_data->eff_link_speed = LINK_DOWN; | 
|  | net_eth_carrier_off(dev_data->iface); | 
|  |  | 
|  | LOG_WRN("%s link down", dev->name); | 
|  | } else { | 
|  | /* | 
|  | * A link has been detected, which, depending | 
|  | * on the driver's configuration, might have | 
|  | * a different speed than the previous link. | 
|  | * Therefore, the clock dividers must be ad- | 
|  | * justed accordingly. | 
|  | */ | 
|  | dev_data->eff_link_speed = | 
|  | dev_data->phy_access_api->phy_poll_link_speed_func(dev); | 
|  |  | 
|  | eth_xlnx_gem_configure_clocks(dev); | 
|  | eth_xlnx_gem_set_nwcfg_link_speed(dev); | 
|  | net_eth_carrier_on(dev_data->iface); | 
|  |  | 
|  | LOG_INF("%s link up, %s", dev->name, | 
|  | (dev_data->eff_link_speed   == LINK_1GBIT) | 
|  | ? "1 GBit/s" | 
|  | : (dev_data->eff_link_speed == LINK_100MBIT) | 
|  | ? "100 MBit/s" | 
|  | : (dev_data->eff_link_speed == LINK_10MBIT) | 
|  | ? "10 MBit/s" : "undefined / link down"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Re-submit the delayed work using the interval from the device | 
|  | * configuration data. | 
|  | */ | 
|  | k_work_reschedule(&dev_data->phy_poll_delayed_work, | 
|  | K_MSEC(dev_conf->phy_poll_interval)); | 
|  | } else { | 
|  | /* | 
|  | * The current driver instance doesn't manage a PHY or no | 
|  | * supported PHY was detected -> pretend the configured max. | 
|  | * link speed is the effective link speed and that the link | 
|  | * is up. The delayed work item won't be re-scheduled, as | 
|  | * there isn't anything to poll for. | 
|  | */ | 
|  | dev_data->eff_link_speed = dev_conf->max_link_speed; | 
|  |  | 
|  | eth_xlnx_gem_configure_clocks(dev); | 
|  | eth_xlnx_gem_set_nwcfg_link_speed(dev); | 
|  | net_eth_carrier_on(dev_data->iface); | 
|  |  | 
|  | LOG_WRN("%s PHY not managed by the driver or no compatible " | 
|  | "PHY detected, assuming link up at %s", dev->name, | 
|  | (dev_conf->max_link_speed == LINK_1GBIT) | 
|  | ? "1 GBit/s" | 
|  | : (dev_conf->max_link_speed == LINK_100MBIT) | 
|  | ? "100 MBit/s" | 
|  | : (dev_conf->max_link_speed == LINK_10MBIT) | 
|  | ? "10 MBit/s" : "undefined"); | 
|  | } | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM DMA memory area setup function | 
|  | * Sets up the DMA memory area to be used by the current GEM device. | 
|  | * Called from within the device initialization function or from within | 
|  | * the context of the PHY status polling delayed work handler. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_configure_buffers(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | struct eth_xlnx_gem_bd *bdptr; | 
|  | uint32_t buf_iter; | 
|  |  | 
|  | /* Initial configuration of the RX/TX BD rings */ | 
|  | DT_INST_FOREACH_STATUS_OKAY(ETH_XLNX_GEM_INIT_BD_RING) | 
|  |  | 
|  | /* | 
|  | * Set initial RX BD data -> comp. Zynq-7000 TRM, Chapter 16.3.5, | 
|  | * "Receive Buffer Descriptor List". The BD ring data other than | 
|  | * the base RX/TX buffer pointers will be set in eth_xlnx_gem_- | 
|  | * iface_init() | 
|  | */ | 
|  | bdptr = dev_data->rxbd_ring.first_bd; | 
|  |  | 
|  | for (buf_iter = 0; buf_iter < (dev_conf->rxbd_count - 1); buf_iter++) { | 
|  | /* Clear 'used' bit -> BD is owned by the controller */ | 
|  | bdptr->ctrl = 0; | 
|  | bdptr->addr = (uint32_t)dev_data->first_rx_buffer + | 
|  | (buf_iter * (uint32_t)dev_conf->rx_buffer_size); | 
|  | ++bdptr; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * For the last BD, bit [1] must be OR'ed in the buffer memory | 
|  | * address -> this is the 'wrap' bit indicating that this is the | 
|  | * last BD in the ring. This location is used as bits [1..0] can't | 
|  | * be part of the buffer address due to alignment requirements | 
|  | * anyways. Watch out: TX BDs handle this differently, their wrap | 
|  | * bit is located in the BD's control word! | 
|  | */ | 
|  | bdptr->ctrl = 0; /* BD is owned by the controller */ | 
|  | bdptr->addr = ((uint32_t)dev_data->first_rx_buffer + | 
|  | (buf_iter * (uint32_t)dev_conf->rx_buffer_size)) | | 
|  | ETH_XLNX_GEM_RXBD_WRAP_BIT; | 
|  |  | 
|  | /* | 
|  | * Set initial TX BD data -> comp. Zynq-7000 TRM, Chapter 16.3.5, | 
|  | * "Transmit Buffer Descriptor List". TX BD ring data has already | 
|  | * been set up in eth_xlnx_gem_iface_init() | 
|  | */ | 
|  | bdptr = dev_data->txbd_ring.first_bd; | 
|  |  | 
|  | for (buf_iter = 0; buf_iter < (dev_conf->txbd_count - 1); buf_iter++) { | 
|  | /* Set up the control word -> 'used' flag must be set. */ | 
|  | bdptr->ctrl = ETH_XLNX_GEM_TXBD_USED_BIT; | 
|  | bdptr->addr = (uint32_t)dev_data->first_tx_buffer + | 
|  | (buf_iter * (uint32_t)dev_conf->tx_buffer_size); | 
|  | ++bdptr; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * For the last BD, set the 'wrap' bit indicating to the controller | 
|  | * that this BD is the last one in the ring. -> For TX BDs, the 'wrap' | 
|  | * bit isn't located in the address word, but in the control word | 
|  | * instead | 
|  | */ | 
|  | bdptr->ctrl = (ETH_XLNX_GEM_TXBD_WRAP_BIT | ETH_XLNX_GEM_TXBD_USED_BIT); | 
|  | bdptr->addr = (uint32_t)dev_data->first_tx_buffer + | 
|  | (buf_iter * (uint32_t)dev_conf->tx_buffer_size); | 
|  |  | 
|  | /* Set free count/current index in the RX/TX BD ring data */ | 
|  | dev_data->rxbd_ring.next_to_process = 0; | 
|  | dev_data->rxbd_ring.next_to_use     = 0; | 
|  | dev_data->rxbd_ring.free_bds        = dev_conf->rxbd_count; | 
|  | dev_data->txbd_ring.next_to_process = 0; | 
|  | dev_data->txbd_ring.next_to_use     = 0; | 
|  | dev_data->txbd_ring.free_bds        = dev_conf->txbd_count; | 
|  |  | 
|  | /* Write pointers to the first RX/TX BD to the controller */ | 
|  | sys_write32((uint32_t)dev_data->rxbd_ring.first_bd, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_RXQBASE_OFFSET); | 
|  | sys_write32((uint32_t)dev_data->txbd_ring.first_bd, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_TXQBASE_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM RX data pending handler wrapper for the work queue | 
|  | * Wraps the RX data pending handler, eth_xlnx_gem_handle_rx_pending, | 
|  | * for the scenario in which the current GEM device is configured | 
|  | * to defer RX pending / TX done indication handling to the system | 
|  | * work queue. In this case, the work item received by this wrapper | 
|  | * function will be enqueued from within the ISR if the corresponding | 
|  | * bit is set within the controller's interrupt status register | 
|  | * (gem.intr_status). | 
|  | * | 
|  | * @param item Pointer to the work item enqueued by the ISR which | 
|  | *             facilitates access to the current device's data | 
|  | */ | 
|  | static void eth_xlnx_gem_rx_pending_work(struct k_work *item) | 
|  | { | 
|  | struct eth_xlnx_gem_dev_data *dev_data = CONTAINER_OF(item, | 
|  | struct eth_xlnx_gem_dev_data, rx_pend_work); | 
|  | const struct device *dev = net_if_get_device(dev_data->iface); | 
|  |  | 
|  | eth_xlnx_gem_handle_rx_pending(dev); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM RX data pending handler | 
|  | * This handler is called either from within the ISR or from the | 
|  | * context of the system work queue whenever the RX data pending bit | 
|  | * is set in the controller's interrupt status register (gem.intr_status). | 
|  | * No further RX data pending interrupts will be triggered until this | 
|  | * handler has been executed, which eventually clears the corresponding | 
|  | * interrupt status bit. This function acquires the incoming packet | 
|  | * data from the DMA memory area via the RX buffer descriptors and copies | 
|  | * the data to a packet which will then be handed over to the network | 
|  | * stack. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_handle_rx_pending(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_addr; | 
|  | uint32_t reg_ctrl; | 
|  | uint32_t reg_val; | 
|  | uint32_t reg_val_rxsr; | 
|  | uint8_t first_bd_idx; | 
|  | uint8_t last_bd_idx; | 
|  | uint8_t	curr_bd_idx; | 
|  | uint32_t rx_data_length; | 
|  | uint32_t rx_data_remaining; | 
|  | struct net_pkt *pkt; | 
|  |  | 
|  | /* Read the RX status register */ | 
|  | reg_val_rxsr = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_RXSR_OFFSET); | 
|  |  | 
|  | /* | 
|  | * TODO Evaluate error flags from RX status register word | 
|  | * here for proper error handling. | 
|  | */ | 
|  |  | 
|  | while (1) { | 
|  | curr_bd_idx = dev_data->rxbd_ring.next_to_process; | 
|  | first_bd_idx = last_bd_idx = curr_bd_idx; | 
|  | reg_addr = (uint32_t)(&dev_data->rxbd_ring.first_bd[first_bd_idx].addr); | 
|  | reg_ctrl = (uint32_t)(&dev_data->rxbd_ring.first_bd[first_bd_idx].ctrl); | 
|  |  | 
|  | /* | 
|  | * Basic precondition checks for the current BD's | 
|  | * address and control words | 
|  | */ | 
|  | reg_val = sys_read32(reg_addr); | 
|  | if ((reg_val & ETH_XLNX_GEM_RXBD_USED_BIT) == 0) { | 
|  | /* | 
|  | * No new data contained in the current BD | 
|  | * -> break out of the RX loop | 
|  | */ | 
|  | break; | 
|  | } | 
|  | reg_val = sys_read32(reg_ctrl); | 
|  | if ((reg_val & ETH_XLNX_GEM_RXBD_START_OF_FRAME_BIT) == 0) { | 
|  | /* | 
|  | * Although the current BD is marked as 'used', it | 
|  | * doesn't contain the SOF bit. | 
|  | */ | 
|  | LOG_ERR("%s unexpected missing SOF bit in RX BD [%u]", | 
|  | dev->name, first_bd_idx); | 
|  | break; | 
|  | } | 
|  |  | 
|  | /* | 
|  | * As long as the current BD doesn't have the EOF bit set, | 
|  | * iterate forwards until the EOF bit is encountered. Only | 
|  | * the BD containing the EOF bit also contains the length | 
|  | * of the received packet which spans multiple buffers. | 
|  | */ | 
|  | do { | 
|  | reg_ctrl = (uint32_t)(&dev_data->rxbd_ring.first_bd[last_bd_idx].ctrl); | 
|  | reg_val  = sys_read32(reg_ctrl); | 
|  | rx_data_length = rx_data_remaining = | 
|  | (reg_val & ETH_XLNX_GEM_RXBD_FRAME_LENGTH_MASK); | 
|  | if ((reg_val & ETH_XLNX_GEM_RXBD_END_OF_FRAME_BIT) == 0) { | 
|  | last_bd_idx = (last_bd_idx + 1) % dev_conf->rxbd_count; | 
|  | } | 
|  | } while ((reg_val & ETH_XLNX_GEM_RXBD_END_OF_FRAME_BIT) == 0); | 
|  |  | 
|  | /* | 
|  | * Store the position of the first BD behind the end of the | 
|  | * frame currently being processed as 'next to process' | 
|  | */ | 
|  | dev_data->rxbd_ring.next_to_process = (last_bd_idx + 1) % | 
|  | dev_conf->rxbd_count; | 
|  |  | 
|  | /* | 
|  | * Allocate a destination packet from the network stack | 
|  | * now that the total frame length is known. | 
|  | */ | 
|  | pkt = net_pkt_rx_alloc_with_buffer(dev_data->iface, rx_data_length, | 
|  | AF_UNSPEC, 0, K_NO_WAIT); | 
|  | if (pkt == NULL) { | 
|  | LOG_ERR("RX packet buffer alloc failed: %u bytes", | 
|  | rx_data_length); | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | dev_data->stats.errors.rx++; | 
|  | dev_data->stats.error_details.rx_no_buffer_count++; | 
|  | #endif | 
|  | } | 
|  |  | 
|  | /* | 
|  | * Copy data from all involved RX buffers into the allocated | 
|  | * packet's data buffer. If we don't have a packet buffer be- | 
|  | * cause none are available, we still have to iterate over all | 
|  | * involved BDs in order to properly release them for re-use | 
|  | * by the controller. | 
|  | */ | 
|  | do { | 
|  | if (pkt != NULL) { | 
|  | net_pkt_write(pkt, (const void *) | 
|  | (dev_data->rxbd_ring.first_bd[curr_bd_idx].addr & | 
|  | ETH_XLNX_GEM_RXBD_BUFFER_ADDR_MASK), | 
|  | (rx_data_remaining < dev_conf->rx_buffer_size) ? | 
|  | rx_data_remaining : dev_conf->rx_buffer_size); | 
|  | } | 
|  | rx_data_remaining -= (rx_data_remaining < dev_conf->rx_buffer_size) ? | 
|  | rx_data_remaining : dev_conf->rx_buffer_size; | 
|  |  | 
|  | /* | 
|  | * The entire packet data of the current BD has been | 
|  | * processed, on to the next BD -> preserve the RX BD's | 
|  | * 'wrap' bit & address, but clear the 'used' bit. | 
|  | */ | 
|  | reg_addr = (uint32_t)(&dev_data->rxbd_ring.first_bd[curr_bd_idx].addr); | 
|  | reg_val	 = sys_read32(reg_addr); | 
|  | reg_val &= ~ETH_XLNX_GEM_RXBD_USED_BIT; | 
|  | sys_write32(reg_val, reg_addr); | 
|  |  | 
|  | curr_bd_idx = (curr_bd_idx + 1) % dev_conf->rxbd_count; | 
|  | } while (curr_bd_idx != ((last_bd_idx + 1) % dev_conf->rxbd_count)); | 
|  |  | 
|  | /* Propagate the received packet to the network stack */ | 
|  | if (pkt != NULL) { | 
|  | if (net_recv_data(dev_data->iface, pkt) < 0) { | 
|  | LOG_ERR("%s RX packet hand-over to IP stack failed", | 
|  | dev->name); | 
|  | net_pkt_unref(pkt); | 
|  | } | 
|  | #ifdef CONFIG_NET_STATISTICS_ETHERNET | 
|  | else { | 
|  | dev_data->stats.bytes.received += rx_data_length; | 
|  | dev_data->stats.pkts.rx++; | 
|  | } | 
|  | #endif | 
|  | } | 
|  | } | 
|  |  | 
|  | /* Clear the RX status register */ | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_RXSR_OFFSET); | 
|  | /* Re-enable the frame received interrupt source */ | 
|  | sys_write32(ETH_XLNX_GEM_IXR_FRAME_RX_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IER_OFFSET); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM TX done handler wrapper for the work queue | 
|  | * Wraps the TX done handler, eth_xlnx_gem_handle_tx_done, | 
|  | * for the scenario in which the current GEM device is configured | 
|  | * to defer RX pending / TX done indication handling to the system | 
|  | * work queue. In this case, the work item received by this wrapper | 
|  | * function will be enqueued from within the ISR if the corresponding | 
|  | * bit is set within the controller's interrupt status register | 
|  | * (gem.intr_status). | 
|  | * | 
|  | * @param item Pointer to the work item enqueued by the ISR which | 
|  | *             facilitates access to the current device's data | 
|  | */ | 
|  | static void eth_xlnx_gem_tx_done_work(struct k_work *item) | 
|  | { | 
|  | struct eth_xlnx_gem_dev_data *dev_data = CONTAINER_OF(item, | 
|  | struct eth_xlnx_gem_dev_data, tx_done_work); | 
|  | const struct device *dev = net_if_get_device(dev_data->iface); | 
|  |  | 
|  | eth_xlnx_gem_handle_tx_done(dev); | 
|  | } | 
|  |  | 
|  | /** | 
|  | * @brief GEM TX done handler | 
|  | * This handler is called either from within the ISR or from the | 
|  | * context of the system work queue whenever the TX done bit is set | 
|  | * in the controller's interrupt status register (gem.intr_status). | 
|  | * No further TX done interrupts will be triggered until this handler | 
|  | * has been executed, which eventually clears the corresponding | 
|  | * interrupt status bit. Once this handler reaches the end of its | 
|  | * execution, the eth_xlnx_gem_send call which effectively triggered | 
|  | * it is unblocked by posting to the current GEM's TX done semaphore | 
|  | * on which the send function is blocking. | 
|  | * | 
|  | * @param dev Pointer to the device data | 
|  | */ | 
|  | static void eth_xlnx_gem_handle_tx_done(const struct device *dev) | 
|  | { | 
|  | const struct eth_xlnx_gem_dev_cfg *dev_conf = DEV_CFG(dev); | 
|  | struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev); | 
|  | uint32_t reg_ctrl; | 
|  | uint32_t reg_val; | 
|  | uint32_t reg_val_txsr; | 
|  | uint8_t curr_bd_idx; | 
|  | uint8_t first_bd_idx; | 
|  | uint8_t bds_processed = 0; | 
|  | uint8_t bd_is_last; | 
|  |  | 
|  | /* Read the TX status register */ | 
|  | reg_val_txsr = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_TXSR_OFFSET); | 
|  |  | 
|  | /* | 
|  | * TODO Evaluate error flags from TX status register word | 
|  | * here for proper error handling | 
|  | */ | 
|  |  | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_sem_take(&(dev_data->txbd_ring.ring_sem), K_FOREVER); | 
|  | } | 
|  |  | 
|  | curr_bd_idx = first_bd_idx = dev_data->txbd_ring.next_to_process; | 
|  | reg_ctrl = (uint32_t)(&dev_data->txbd_ring.first_bd[curr_bd_idx].ctrl); | 
|  | reg_val  = sys_read32(reg_ctrl); | 
|  |  | 
|  | do { | 
|  | ++bds_processed; | 
|  |  | 
|  | /* | 
|  | * TODO Evaluate error flags from current BD control word | 
|  | * here for proper error handling | 
|  | */ | 
|  |  | 
|  | /* | 
|  | * Check if the BD we're currently looking at is the last BD | 
|  | * of the current transmission | 
|  | */ | 
|  | bd_is_last = ((reg_val & ETH_XLNX_GEM_TXBD_LAST_BIT) != 0) ? 1 : 0; | 
|  |  | 
|  | /* | 
|  | * Reset control word of the current BD, clear everything but | 
|  | * the 'wrap' bit, then set the 'used' bit | 
|  | */ | 
|  | reg_val &= ETH_XLNX_GEM_TXBD_WRAP_BIT; | 
|  | reg_val |= ETH_XLNX_GEM_TXBD_USED_BIT; | 
|  | sys_write32(reg_val, reg_ctrl); | 
|  |  | 
|  | /* Move on to the next BD or break out of the loop */ | 
|  | if (bd_is_last == 1) { | 
|  | break; | 
|  | } | 
|  | curr_bd_idx = (curr_bd_idx + 1) % dev_conf->txbd_count; | 
|  | reg_ctrl = (uint32_t)(&dev_data->txbd_ring.first_bd[curr_bd_idx].ctrl); | 
|  | reg_val  = sys_read32(reg_ctrl); | 
|  | } while (bd_is_last == 0 && curr_bd_idx != first_bd_idx); | 
|  |  | 
|  | if (curr_bd_idx == first_bd_idx && bd_is_last == 0) { | 
|  | LOG_WRN("%s TX done handling wrapped around", dev->name); | 
|  | } | 
|  |  | 
|  | dev_data->txbd_ring.next_to_process = | 
|  | (dev_data->txbd_ring.next_to_process + bds_processed) % | 
|  | dev_conf->txbd_count; | 
|  | dev_data->txbd_ring.free_bds += bds_processed; | 
|  |  | 
|  | if (dev_conf->defer_txd_to_queue) { | 
|  | k_sem_give(&(dev_data->txbd_ring.ring_sem)); | 
|  | } | 
|  |  | 
|  | /* Clear the TX status register */ | 
|  | sys_write32(0xFFFFFFFF, dev_conf->base_addr + ETH_XLNX_GEM_TXSR_OFFSET); | 
|  |  | 
|  | /* Re-enable the TX complete interrupt source */ | 
|  | sys_write32(ETH_XLNX_GEM_IXR_TX_COMPLETE_BIT, | 
|  | dev_conf->base_addr + ETH_XLNX_GEM_IER_OFFSET); | 
|  |  | 
|  | /* Indicate completion to a blocking eth_xlnx_gem_send() call */ | 
|  | k_sem_give(&dev_data->tx_done_sem); | 
|  | } | 
|  |  | 
|  | /* EOF */ |