diff --git a/boards/qemu/cortex_a9/Kconfig.defconfig b/boards/qemu/cortex_a9/Kconfig.defconfig index c2051fe54087f..83330b8ffc145 100644 --- a/boards/qemu/cortex_a9/Kconfig.defconfig +++ b/boards/qemu/cortex_a9/Kconfig.defconfig @@ -29,6 +29,9 @@ if NETWORKING config NET_L2_ETHERNET default y +config MDIO + default y + config NET_TX_STACK_SIZE default 8192 diff --git a/boards/qemu/cortex_a9/qemu_cortex_a9.dts b/boards/qemu/cortex_a9/qemu_cortex_a9.dts index 8a12ba3080299..02f5d163b34ed 100644 --- a/boards/qemu/cortex_a9/qemu_cortex_a9.dts +++ b/boards/qemu/cortex_a9/qemu_cortex_a9.dts @@ -57,7 +57,17 @@ &gem0 { status = "okay"; + phy-handle = <&gem0_phy>; clock-frequency = <1000000000>; - mdc-divider = ; local-mac-address = [00 00 00 01 02 03]; }; + +&gem0_mdio { + status = "okay"; + + gem0_phy: phy@1 { + compatible = "marvell,88e1xxx"; + reg = <1>; + status = "okay"; + }; +}; diff --git a/drivers/ethernet/CMakeLists.txt b/drivers/ethernet/CMakeLists.txt index f9cef24042b5e..a5e4f0b06934d 100644 --- a/drivers/ethernet/CMakeLists.txt +++ b/drivers/ethernet/CMakeLists.txt @@ -13,11 +13,7 @@ zephyr_library_sources_ifdef(CONFIG_ETH_GECKO phy_gecko.c ) -zephyr_library_sources_ifdef(CONFIG_ETH_XLNX_GEM - eth_xlnx_gem.c - phy_xlnx_gem.c - ) - +zephyr_library_sources_ifdef(CONFIG_ETH_XLNX_GEM eth_xlnx_gem.c) zephyr_library_sources_ifdef(CONFIG_ETH_DWMAC eth_dwmac.c) zephyr_library_sources_ifdef(CONFIG_ETH_DWMAC_STM32H7X eth_dwmac_stm32h7x.c) zephyr_library_sources_ifdef(CONFIG_ETH_DWMAC_MMU eth_dwmac_mmu.c) diff --git a/drivers/ethernet/eth_xlnx_gem.c b/drivers/ethernet/eth_xlnx_gem.c index 1a99dbbe393fd..37ff18764ae49 100644 --- a/drivers/ethernet/eth_xlnx_gem.c +++ b/drivers/ethernet/eth_xlnx_gem.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -51,33 +52,38 @@ static int eth_xlnx_gem_get_config(const struct device *dev, static int eth_xlnx_gem_set_config(const struct device *dev, enum ethernet_config_type type, const struct ethernet_config *config); -#if defined(CONFIG_NET_STATISTICS_ETHERNET) +static const struct device *eth_xlnx_gem_get_phy(const struct device *dev); +#ifdef 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 void eth_xlnx_gem_configure_clocks(const struct device *dev, + struct phy_link_state *state); +static void eth_xlnx_gem_set_nwcfg_link_speed(const struct device *dev, + struct phy_link_state *state); +static void eth_xlnx_gem_phy_cb(const struct device *phy, + struct phy_link_state *state, + void *eth_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, + .get_phy = eth_xlnx_gem_get_phy, .send = eth_xlnx_gem_send, .start = eth_xlnx_gem_start_device, .stop = eth_xlnx_gem_stop_device, .get_config = eth_xlnx_gem_get_config, .set_config = eth_xlnx_gem_set_config, -#if defined(CONFIG_NET_STATISTICS_ETHERNET) +#ifdef CONFIG_NET_STATISTICS_ETHERNET .get_stats = eth_xlnx_gem_stats, #endif }; @@ -90,8 +96,8 @@ 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. + * Initializes the respective GEM controller instance itself and its + * associated DMA area. * * @param dev Pointer to the device data * @retval 0 if the device initialization completed successfully @@ -99,42 +105,9 @@ DT_INST_FOREACH_STATUS_OKAY(ETH_XLNX_GEM_INITIALIZE) static int eth_xlnx_gem_dev_init(const struct device *dev) { const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - 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_FAMILY_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 || @@ -193,26 +166,16 @@ static int eth_xlnx_gem_dev_init(const struct device *dev) /* * Initialization procedure as described in the Zynq-7000 TRM, - * chapter 16.3.x. + * chapter 16.3.x. MDIO initialization (16.3.4) is handled prior + * to the following initialization procedure within the separate + * GEM MDIO driver. TX clock divisor configuration (16.3.3) is + * handled from within the PHY state change callback function + * (if applicable). */ 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; @@ -229,21 +192,20 @@ 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->config; struct eth_xlnx_gem_dev_data *dev_data = dev->data; + struct phy_link_state state = {}; + int ret; /* 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_carrier_off(iface); /* - * Initialize the (delayed) work items for RX pending, TX done - * and PHY status polling handlers + * Initialize the (delayed) work items for RX pending and TX done + * handling. */ 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); @@ -258,8 +220,30 @@ static void eth_xlnx_gem_iface_init(struct net_if *iface) /* 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); + /* + * If PHY is available: register callback & pick up current link state immediately. + * For PHY-less operation: fixed link is pre-configured, indicate link up. + */ + if (dev_conf->phy_dev != NULL) { + net_eth_carrier_off(iface); + + ret = phy_link_callback_set(dev_conf->phy_dev, eth_xlnx_gem_phy_cb, + (void *)dev); + if (ret) { + LOG_ERR("%s: set PHY callback failed", dev->name); + return ret; + } + + ret = phy_get_link_state(dev_conf->phy_dev, &state); + if (ret) { + LOG_ERR("%s: get PHY link state failed", dev->name); + return ret; + } + + eth_xlnx_gem_phy_cb(dev_conf->phy_dev, &state, (void *)dev); + } else { + net_eth_carrier_on(iface); + } } /** @@ -367,8 +351,7 @@ static int eth_xlnx_gem_send(const struct device *dev, struct net_pkt *pkt) 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))) { + if (!dev_data->started || !net_if_flag_is_set(dev_data->iface, NET_IF_UP)) { #ifdef CONFIG_NET_STATISTICS_ETHERNET dev_data->stats.tx_dropped++; #endif @@ -555,11 +538,6 @@ static int eth_xlnx_gem_start_device(const struct device *dev) 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; } @@ -585,11 +563,6 @@ static int eth_xlnx_gem_stop_device(const struct device *dev) } 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)); @@ -624,22 +597,9 @@ static enum ethernet_hw_caps eth_xlnx_gem_get_capabilities( const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; 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 | ETHERNET_LINK_100BASE | - ETHERNET_LINK_10BASE); - } else { - caps |= ETHERNET_LINK_1000BASE; - } - } else if (dev_conf->max_link_speed == LINK_100MBIT) { - if (dev_conf->phy_advertise_lower) { - caps |= (ETHERNET_LINK_100BASE | ETHERNET_LINK_10BASE); - } else { - caps |= ETHERNET_LINK_100BASE; - } - } else { - caps |= ETHERNET_LINK_10BASE; - } + caps |= ETHERNET_LINK_1000BASE | + ETHERNET_LINK_100BASE | + ETHERNET_LINK_10BASE; if (dev_conf->enable_rx_chksum_offload) { caps |= ETHERNET_HW_RX_CHKSUM_OFFLOAD; @@ -649,15 +609,23 @@ static enum ethernet_hw_caps eth_xlnx_gem_get_capabilities( caps |= ETHERNET_HW_TX_CHKSUM_OFFLOAD; } - if (dev_conf->enable_fdx) { - caps |= ETHERNET_DUPLEX_SET; - } - caps |= ETHERNET_PROMISC_MODE; return caps; } +/** + * @brief Returns a pointer to the associated PHY device + * @param dev Parent GEM device of the requested PHY device + * @return Pointer to the associated PHY device + */ +static const struct device *eth_xlnx_gem_get_phy(const struct device *dev) +{ + const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; + + return dev_conf->phy_dev; +} + /** * @brief GEM hardware configuration data request function * Returns hardware configuration details of the specified device @@ -785,19 +753,18 @@ static struct net_stats_eth *eth_xlnx_gem_stats(const struct device *dev) static void eth_xlnx_gem_reset_hw(const struct device *dev) { const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; + uint32_t nwctrl; /* * 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); + /* Prepare the NWCTRL register, preserve the MDEN bit */ + nwctrl = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); + nwctrl &= ETH_XLNX_GEM_NWCTRL_MDEN_BIT; + nwctrl |= ETH_XLNX_GEM_NWCTRL_STATCLR_BIT; /* clear statistics counters */ + sys_write32(nwctrl, dev_conf->base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); /* Clear the RX/TX status registers */ sys_write32(ETH_XLNX_GEM_TXSRCLR_MASK, @@ -823,8 +790,10 @@ static void eth_xlnx_gem_reset_hw(const struct device *dev) * from within the device initialization function. * * @param dev Pointer to the device data + * @param state pointer to the current PHY link state data */ -static void eth_xlnx_gem_configure_clocks(const struct device *dev) +static void eth_xlnx_gem_configure_clocks(const struct device *dev, + struct phy_link_state *state) { /* * Clock source configuration for the respective GEM as described @@ -834,7 +803,6 @@ static void eth_xlnx_gem_configure_clocks(const struct device *dev) */ const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; uint32_t div0; uint32_t div1; @@ -842,34 +810,12 @@ static void eth_xlnx_gem_configure_clocks(const struct device *dev) 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 */ - } + if (PHY_LINK_IS_SPEED_1000M(state->speed)) { + target = 125000000; /* Target frequency: 125 MHz */ + } else if (PHY_LINK_IS_SPEED_100M(state->speed)) { + target = 25000000; /* Target frequency: 25 MHz */ + } else { + target = 2500000; /* Target frequency: 2.5 MHz */ } /* @@ -953,7 +899,9 @@ static void eth_xlnx_gem_configure_clocks(const struct device *dev) static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev) { const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - uint32_t reg_val = 0; + uint32_t reg_val = sys_read32(dev_conf->base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); + + reg_val &= (ETH_XLNX_GEM_NWCFG_MDC_MASK << ETH_XLNX_GEM_NWCFG_MDC_SHIFT); if (dev_conf->ignore_ipg_rxer) { /* [30] ignore IPG rx_er */ @@ -991,10 +939,6 @@ static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev) 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; @@ -1043,17 +987,6 @@ static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev) /* [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); @@ -1065,26 +998,34 @@ static void eth_xlnx_gem_set_initial_nwcfg(const struct device *dev) * register. This is called from within #eth_xlnx_gem_poll_phy. * * @param dev Pointer to the device data + * @param state pointer to the current PHY link state data */ -static void eth_xlnx_gem_set_nwcfg_link_speed(const struct device *dev) +static void eth_xlnx_gem_set_nwcfg_link_speed(const struct device *dev, + struct phy_link_state *state) { const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; uint32_t reg_val; /* - * Read the current gem.net_cfg register contents and mask out - * the link speed-related bits + * Read the current gem.net_cfg register, mask out the link speed + * and duplex related bits. Replace their contents with those + * matching the current PHY state. */ 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); + reg_val &= ~(ETH_XLNX_GEM_NWCFG_1000_BIT | + ETH_XLNX_GEM_NWCFG_100_BIT | + ETH_XLNX_GEM_NWCFG_FDEN_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) { + if (PHY_LINK_IS_SPEED_100M(state->speed)) { reg_val |= ETH_XLNX_GEM_NWCFG_100_BIT; - } else if (dev_data->eff_link_speed == LINK_1GBIT) { + } else if (PHY_LINK_IS_SPEED_1000M(state->speed)) { reg_val |= ETH_XLNX_GEM_NWCFG_1000_BIT; } + /* Set FDEN bit for full-duplex operation */ + if (PHY_LINK_IS_FULL_DUPLEX(state->speed)) { + reg_val |= ETH_XLNX_GEM_NWCFG_FDEN_BIT; + } /* Write the assembled register contents to gem.net_cfg */ sys_write32(reg_val, dev_conf->base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); @@ -1194,154 +1135,6 @@ static void eth_xlnx_gem_set_initial_dmacr(const struct device *dev) 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; - 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->config; - - 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. @@ -1727,3 +1520,32 @@ static void eth_xlnx_gem_handle_tx_done(const struct device *dev) /* Indicate completion to a blocking eth_xlnx_gem_send() call */ k_sem_give(&dev_data->tx_done_sem); } + +/** + * @brief PHY event callback function + * This handler function is called whenever a link state change or a + * link speed change is indicated by the associated PHY. + * + * @param dev Pointer to the device data + * @param state Updated PHY link/speed state + * @param eth_dev Pointer to the GEM instance's device struct + */ +static void eth_xlnx_gem_phy_cb(const struct device *phy, + struct phy_link_state *state, + void *eth_dev) +{ + const struct device *dev = (const struct device *)eth_dev; + struct eth_xlnx_gem_dev_data *dev_data = dev->data; + + if (dev_data->iface == NULL) { + return; + } + + if (state->is_up) { + eth_xlnx_gem_configure_clocks(dev, state); + eth_xlnx_gem_set_nwcfg_link_speed(dev, state); + net_eth_carrier_on(dev_data->iface); + } else { + net_eth_carrier_off(dev_data->iface); + } +} diff --git a/drivers/ethernet/eth_xlnx_gem_priv.h b/drivers/ethernet/eth_xlnx_gem_priv.h index a4cb88f93b98c..5ec2409132feb 100644 --- a/drivers/ethernet/eth_xlnx_gem_priv.h +++ b/drivers/ethernet/eth_xlnx_gem_priv.h @@ -17,8 +17,6 @@ #include #include -#include "phy_xlnx_gem.h" - #define ETH_XLNX_BUFFER_ALIGNMENT 4 /* RX/TX buffer alignment (in bytes) */ /* Buffer descriptor (BD) related defines */ @@ -277,7 +275,6 @@ #define ETH_XLNX_GEM_NWCFG_DBUSW_SHIFT 21 #define ETH_XLNX_GEM_NWCFG_MDC_MASK 0x7 #define ETH_XLNX_GEM_NWCFG_MDC_SHIFT 18 -#define ETH_XLNX_GEM_NWCFG_MDCCLKDIV_MASK 0x001C0000 #define ETH_XLNX_GEM_NWCFG_FCSREM_BIT 0x00020000 #define ETH_XLNX_GEM_NWCFG_LENGTHERRDSCRD_BIT 0x00010000 #define ETH_XLNX_GEM_NWCFG_RXOFFS_MASK 0x00000003 @@ -374,37 +371,8 @@ #define ETH_XLNX_GEM_IXR_ALL_MASK 0x03FC7FFE #define ETH_XLNX_GEM_IXR_ERRORS_MASK 0x00000C60 -/* Bits / bit masks relating to the GEM's MDIO interface */ - -/* - * gem.net_status: - * [02] PHY management idle bit - * [01] MDIO input status - */ -#define ETH_XLNX_GEM_MDIO_IDLE_BIT 0x00000004 -#define ETH_XLNX_GEM_MDIO_IN_STATUS_BIT 0x00000002 - -/* - * gem.phy_maint: - * [31 .. 30] constant values - * [17 .. 16] constant values - * [29] Read operation control bit - * [28] Write operation control bit - * [27 .. 23] PHY address - * [22 .. 18] Register address - * [15 .. 00] 16-bit data word - */ -#define ETH_XLNX_GEM_PHY_MAINT_CONST_BITS 0x40020000 -#define ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT 0x20000000 -#define ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT 0x10000000 -#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK 0x0000001F -#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT 23 -#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK 0x0000001F -#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT 18 -#define ETH_XLNX_GEM_PHY_MAINT_DATA_MASK 0x0000FFFF - /* Device initialization macro */ -#define ETH_XLNX_GEM_NET_DEV_INIT(port) \ +#define ETH_XLNX_GEM_NET_DEV_INIT(port)\ ETH_NET_DEVICE_DT_INST_DEFINE(port,\ eth_xlnx_gem_dev_init,\ NULL,\ @@ -414,21 +382,22 @@ ETH_NET_DEVICE_DT_INST_DEFINE(port,\ ð_xlnx_gem_apis,\ NET_ETH_MTU); +/* Helpers for resolving the MDIO and PHY devices */ +#define ETH_XLNX_GEM_MDIO_DEV(port)\ + DEVICE_DT_GET_OR_NULL(DT_NODELABEL(gem##port##_mdio)) + +#define ETH_XLNX_GEM_PHY_DEV(port)\ + DEVICE_DT_GET_OR_NULL(DT_INST_PHANDLE(port, phy_handle)) + /* Device configuration data declaration macro */ #define ETH_XLNX_GEM_DEV_CONFIG(port) \ static const struct eth_xlnx_gem_dev_cfg eth_xlnx_gem##port##_dev_cfg = {\ + .mdio_dev = ETH_XLNX_GEM_MDIO_DEV(port),\ + .phy_dev = ETH_XLNX_GEM_PHY_DEV(port),\ .base_addr = DT_REG_ADDR_BY_IDX(DT_INST(port, xlnx_gem), 0),\ .config_func = eth_xlnx_gem##port##_irq_config,\ .pll_clock_frequency = DT_INST_PROP(port, clock_frequency),\ .clk_ctrl_reg_address = DT_REG_ADDR_BY_IDX(DT_INST(port, xlnx_gem), 1),\ - .mdc_divider = (enum eth_xlnx_mdc_clock_divider)\ - (DT_INST_PROP(port, mdc_divider)),\ - .max_link_speed = (enum eth_xlnx_link_speed)\ - (DT_INST_PROP(port, link_speed)),\ - .init_phy = DT_INST_PROP(port, init_mdio_phy),\ - .phy_mdio_addr_fix = DT_INST_PROP(port, mdio_phy_address),\ - .phy_advertise_lower = DT_INST_PROP(port, advertise_lower_link_speeds),\ - .phy_poll_interval = DT_INST_PROP(port, phy_poll_interval),\ .defer_rxp_to_queue = !DT_INST_PROP(port, handle_rx_in_isr),\ .defer_txd_to_queue = DT_INST_PROP(port, handle_tx_in_workq),\ .amba_dbus_width = (enum eth_xlnx_amba_dbus_width)\ @@ -465,7 +434,6 @@ static const struct eth_xlnx_gem_dev_cfg eth_xlnx_gem##port##_dev_cfg = {\ .enable_mcast_hash = DT_INST_PROP(port, multicast_hash),\ .disable_bcast = DT_INST_PROP(port, reject_broadcast),\ .discard_non_vlan = DT_INST_PROP(port, discard_non_vlan),\ - .enable_fdx = DT_INST_PROP(port, full_duplex),\ .disc_rx_ahb_unavail = DT_INST_PROP(port, discard_rx_frame_ahb_unavail),\ .enable_tx_chksum_offload = DT_INST_PROP(port, tx_checksum_offload),\ .tx_buffer_size_full = DT_INST_PROP(port, hw_tx_buffer_size_full),\ @@ -478,10 +446,6 @@ static const struct eth_xlnx_gem_dev_cfg eth_xlnx_gem##port##_dev_cfg = {\ static struct eth_xlnx_gem_dev_data eth_xlnx_gem##port##_dev_data = {\ .mac_addr = DT_INST_PROP(port, local_mac_address),\ .started = 0,\ - .eff_link_speed = LINK_DOWN,\ - .phy_addr = 0,\ - .phy_id = 0,\ - .phy_access_api = NULL,\ .first_rx_buffer = NULL,\ .first_tx_buffer = NULL\ }; @@ -534,27 +498,13 @@ ETH_XLNX_GEM_DEV_CONFIG(port);\ ETH_XLNX_GEM_DEV_DATA(port);\ ETH_XLNX_GEM_DMA_AREA_DECL(port);\ ETH_XLNX_GEM_DMA_AREA_INST(port);\ -ETH_XLNX_GEM_NET_DEV_INIT(port);\ +ETH_XLNX_GEM_NET_DEV_INIT(port); /* IRQ handler function type */ typedef void (*eth_xlnx_gem_config_irq_t)(const struct device *dev); /* Enums for bitfields representing configuration settings */ -/** - * @brief Link speed configuration enumeration type. - * - * Enumeration type for link speed indication, contains 'link down' - * plus all link speeds supported by the controller (10/100/1000). - */ -enum eth_xlnx_link_speed { - /* The values of this enum are consecutively numbered */ - LINK_DOWN = 0, - LINK_10MBIT, - LINK_100MBIT, - LINK_1GBIT -}; - /** * @brief AMBA AHB data bus width configuration enumeration type. * @@ -569,29 +519,6 @@ enum eth_xlnx_amba_dbus_width { AMBA_AHB_DBUS_WIDTH_128BIT }; -/** - * @brief MDC clock divider configuration enumeration type. - * - * Enumeration type containing the supported clock divider values - * used to generate the MDIO interface clock (MDC) from either the - * cpu_1x clock (Zynq-7000) or the LPD LSBUS clock (UltraScale). - * This is a configuration item in the controller's net_cfg register. - */ -enum eth_xlnx_mdc_clock_divider { - /* The values of this enum are consecutively numbered */ - MDC_DIVIDER_8 = 0, - MDC_DIVIDER_16, - MDC_DIVIDER_32, - MDC_DIVIDER_48, -#ifdef CONFIG_SOC_FAMILY_XILINX_ZYNQ7000 - /* Dividers > 48 are only available in the Zynq-7000 */ - MDC_DIVIDER_64, - MDC_DIVIDER_96, - MDC_DIVIDER_128, - MDC_DIVIDER_224 -#endif -}; - /** * @brief DMA RX buffer size configuration enumeration type. * @@ -679,18 +606,14 @@ struct eth_xlnx_gem_bdring { * UltraScale SoCs, which both contain the GEM. */ struct eth_xlnx_gem_dev_cfg { + const struct device *mdio_dev; + const struct device *phy_dev; uint32_t base_addr; eth_xlnx_gem_config_irq_t config_func; uint32_t pll_clock_frequency; uint32_t clk_ctrl_reg_address; - enum eth_xlnx_mdc_clock_divider mdc_divider; - enum eth_xlnx_link_speed max_link_speed; - bool init_phy; - uint8_t phy_mdio_addr_fix; - uint8_t phy_advertise_lower; - uint32_t phy_poll_interval; uint8_t defer_rxp_to_queue; uint8_t defer_txd_to_queue; @@ -740,17 +663,11 @@ struct eth_xlnx_gem_dev_cfg { struct eth_xlnx_gem_dev_data { struct net_if *iface; uint8_t mac_addr[6]; - enum eth_xlnx_link_speed eff_link_speed; struct k_work tx_done_work; struct k_work rx_pend_work; struct k_sem tx_done_sem; - uint8_t phy_addr; - uint32_t phy_id; - struct k_work_delayable phy_poll_delayed_work; - struct phy_xlnx_gem_api *phy_access_api; - uint8_t *first_rx_buffer; uint8_t *first_tx_buffer; diff --git a/drivers/ethernet/phy/CMakeLists.txt b/drivers/ethernet/phy/CMakeLists.txt index 34d769eca6c56..a5e1e440ee53b 100644 --- a/drivers/ethernet/phy/CMakeLists.txt +++ b/drivers/ethernet/phy/CMakeLists.txt @@ -5,12 +5,14 @@ zephyr_library_sources_ifdef(CONFIG_PHY_GENERIC_MII phy_mii.c) # zephyr-keep-sorted-start zephyr_library_sources_ifdef(CONFIG_PHY_ADIN2111 phy_adin2111.c) zephyr_library_sources_ifdef(CONFIG_PHY_DM8806 phy_dm8806.c) +zephyr_library_sources_ifdef(CONFIG_PHY_MARVELL_ALASKA_88E1XXX phy_marvell_88e1xxx.c) zephyr_library_sources_ifdef(CONFIG_PHY_MICROCHIP_KSZ8081 phy_microchip_ksz8081.c) zephyr_library_sources_ifdef(CONFIG_PHY_MICROCHIP_T1S phy_microchip_t1s.c) zephyr_library_sources_ifdef(CONFIG_PHY_MICROCHIP_VSC8541 phy_microchip_vsc8541.c) zephyr_library_sources_ifdef(CONFIG_PHY_OA_TC14_PLCA_LIB phy_oa_tc14_plca.c) zephyr_library_sources_ifdef(CONFIG_PHY_QUALCOMM_AR8031 phy_qualcomm_ar8031.c) zephyr_library_sources_ifdef(CONFIG_PHY_REALTEK_RTL8211F phy_realtek_rtl8211f.c) +zephyr_library_sources_ifdef(CONFIG_PHY_TI_DP83822 phy_ti_dp83822.c) zephyr_library_sources_ifdef(CONFIG_PHY_TI_DP83825 phy_ti_dp83825.c) zephyr_library_sources_ifdef(CONFIG_PHY_TI_DP83867 phy_ti_dp83867.c) zephyr_library_sources_ifdef(CONFIG_PHY_TJA1103 phy_tja1103.c) diff --git a/drivers/ethernet/phy/Kconfig b/drivers/ethernet/phy/Kconfig index c9d0c61baa227..3a16e08b39f67 100644 --- a/drivers/ethernet/phy/Kconfig +++ b/drivers/ethernet/phy/Kconfig @@ -96,6 +96,22 @@ config PHY_QUALCOMM_AR8031 help Enable Qualcomm Atheros AR8031 Ethernet PHY Driver +config PHY_MARVELL_ALASKA_88E1XXX + bool "Marvell Alaska 88E1xxx PHY Driver" + default y + depends on DT_HAS_MARVELL_88E1XXX_ENABLED + depends on MDIO + help + Enable Marvell Alaska 88E1xxx PHY Driver + +config PHY_TI_DP83822 + bool "TI DP83822 PHY Driver" + default y + depends on DT_HAS_TI_DP83822_ENABLED + depends on MDIO + help + Enable TI DP83822 PHY Driver + config PHY_AUTONEG_TIMEOUT_MS int "Auto-negotiation timeout value in milliseconds" default 4000 diff --git a/drivers/ethernet/phy/phy_marvell_88e1xxx.c b/drivers/ethernet/phy/phy_marvell_88e1xxx.c new file mode 100644 index 0000000000000..46889c6bfa110 --- /dev/null +++ b/drivers/ethernet/phy/phy_marvell_88e1xxx.c @@ -0,0 +1,626 @@ +/* + * Copyright (c) 2021 Weidmueller Interface GmbH & Co. KG + * Copyright (c) 2025 Immo Birnbaum + * + * Definitions and procedures moved here from the former proprietary + * Xilinx GEM PHY driver (phy_xlnx_gem.c), adjusted to use the MDIO + * framework provided by Zephyr. The TI DP83825 driver was used as a + * template, which is (c) Bernhard Kraemer. + * + * Register IDs & procedures are based on the corresponding datasheets: + * https://www.marvell.com/content/dam/marvell/en/public-collateral/transceivers/marvell-phys-transceivers-alaska-88e1111-datasheet.pdf + * https://www.marvell.com/content/dam/marvell/en/public-collateral/phys-transceivers/marvell-phys-transceivers-alaska-88e151x-datasheet.pdf + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define DT_DRV_COMPAT marvell_88e1xxx + +#include +#include +#include +#include + +#define DT_DRV_COMPAT marvell_88e1xxx +#define LOG_MODULE_NAME phy_marvell_88e1xxx + +#define LOG_LEVEL CONFIG_PHY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +/* Marvell PHY ID: bits [3..0] = revision -> discard during ID check */ +#define PHY_MRVL_PHY_ID_MODEL_MASK 0xFFFFFFF0 +#define PHY_MRVL_PHY_ID_MODEL_88E151X 0x01410DD0 + +#define PHY_MRVL_BASE_REGISTERS_PAGE 0 +#define PHY_MRVL_COPPER_CONTROL_REGISTER MII_BMCR +#define PHY_MRVL_COPPER_AUTONEG_ADV_REGISTER MII_ANAR +#define PHY_MRVL_1000BASET_CONTROL_REGISTER 0x09 +#define PHY_MRVL_COPPER_CONTROL_1_REGISTER 0x10 +#define PHY_MRVL_COPPER_STATUS_1_REGISTER 0x11 +#define PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER 0x16 + +#define PHY_MRVL_GENERAL_CONTROL_1_PAGE 0x12 +#define PHY_MRVL_GENERAL_CONTROL_1_REGISTER 0x14 + +#define PHY_MRVL_COPPER_CONTROL_RESET_BIT BIT(15) +#define PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT BIT(12) + +#define PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT BIT(15) + +#define PHY_MRVL_ADVERTISE_1000_FULL BIT(9) +#define PHY_MRVL_ADVERTISE_1000_HALF BIT(8) + +#define PHY_MRVL_MDIX_CONFIG_MASK 0x0003 +#define PHY_MRVL_MDIX_CONFIG_SHIFT 5 +#define PHY_MRVL_MDIX_AUTO_CROSSOVER_ENABLE 0x0003 +#define PHY_MRVL_MODE_CONFIG_MASK 0x0007 +#define PHY_MRVL_MODE_CONFIG_SHIFT 0 + +#define PHY_MRVL_LINK_SPEED_SHIFT 14 +#define PHY_MRVL_LINK_SPEED_MASK 0x3 +#define PHY_MRVL_LINK_SPEED_10MBIT 0 +#define PHY_MRVL_LINK_SPEED_100MBIT BIT(0) +#define PHY_MRVL_LINK_SPEED_1GBIT BIT(1) +#define PHY_MRVL_LINK_DUPLEX_FDX BIT(13) +#define PHY_MRVL_LINK_STATUS BIT(10) + +struct marvell_alaska_config { + const struct device *mdio_dev; + uint8_t addr; +}; + +struct marvell_alaska_data { + const struct device *dev; + + struct phy_link_state state; + phy_callback_t cb; + void *cb_data; + + struct k_mutex mutex; + struct k_work_delayable phy_monitor_work; + + uint32_t phy_id; +}; + +static int phy_marvell_alaska_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) +{ + const struct marvell_alaska_config *const dev_conf = dev->config; + *data = 0U; /* Clear unused bits [31..16] */ + + return mdio_read(dev_conf->mdio_dev, dev_conf->addr, reg_addr, (uint16_t *)data); +} + +static int phy_marvell_alaska_write(const struct device *dev, uint16_t reg_addr, uint32_t data) +{ + const struct marvell_alaska_config *const dev_conf = dev->config; + + return mdio_write(dev_conf->mdio_dev, dev_conf->addr, reg_addr, (uint16_t)data); +} + +static int phy_marvell_alaska_reset(const struct device *dev) +{ + int ret = 0; + uint32_t phy_data; + uint32_t poll_cnt = 0; + + /* + * Page 0, register address 0 = Copper control register, + * bit [15] = PHY Software reset. Register 0/0 access is R/M/W. + * Comp. datasheet chapter 2.6 and table 64 "Copper Control + * Register". Triggering a PHY Software reset affects pages + * 0, 2, 3, 5, 7. + */ + + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: read Copper Control Register failed", dev->name); + return ret; + } + + phy_data |= PHY_MRVL_COPPER_CONTROL_RESET_BIT; + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: write Copper Control Register failed", dev->name); + return ret; + } + + while (((phy_data & PHY_MRVL_COPPER_CONTROL_RESET_BIT) != 0) && (poll_cnt++ < 10)) { + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: read Copper Control Register " + "(poll completion) failed", + dev->name); + return ret; + } + } + if (poll_cnt == 10) { + LOG_ERR("%s: reset PHY: reset completion timed out", dev->name); + ret = -ETIMEDOUT; + } + + return ret; +} + +static int phy_marvell_alaska_autonegotiate(const struct device *dev) +{ + int ret = 0; + uint32_t phy_data; + + /* + * Trigger a PHY reset, affecting pages 0, 2, 3, 5, 7. + * Afterwards, set the auto-negotiation enable bit [12] in the + * Copper Control Register. + */ + ret = phy_marvell_alaska_reset(dev); + if (ret != 0) { + LOG_ERR("%s: start auto-neg: reset call within " + "%s failed", dev->name, __func__); + + return ret; + } + + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: start auto-neg: read Copper Control Register failed", dev->name); + return ret; + } + + phy_data |= PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT; + + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: start auto-neg: write Copper Control Register failed", dev->name); + return ret; + } + + return 0; +} + +static int phy_marvell_alaska_static_cfg(const struct device *dev) +{ + struct marvell_alaska_data *dev_data = dev->data; + + int ret; + uint32_t phy_data; + uint32_t phy_id; + uint32_t poll_cnt = 0; + + /* Read & store PHY ID */ + ret = phy_marvell_alaska_read(dev, MII_PHYID1R, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read PHYID1R failed", dev->name); + return ret; + } + phy_id = ((phy_data << 16) & 0xFFFF0000); + ret = phy_marvell_alaska_read(dev, MII_PHYID2R, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read PHYID2R failed", dev->name); + return ret; + } + phy_id |= (phy_data & 0x0000FFFF); + + LOG_DBG("%s: configure PHY: read PHY ID 0x%08X", dev->name, phy_id); + + if (phy_id == 0 || phy_id == 0xFFFFFFFF) { + LOG_ERR("%s: configure PHY: no reply from PHY while reading PHY ID", dev->name); + return -EIO; + } + + dev_data->phy_id = phy_id; + + /* + * Page 0, register address 0 = Copper control register, + * bit [12] = auto-negotiation enable bit is to be cleared + * for now, afterwards, trigger a PHY Software reset. + * Register 0/0 access is R/M/W. Comp. datasheet chapter 2.6 + * and table 64 "Copper Control Register". + */ + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read Copper Control Register failed", dev->name); + return ret; + } + phy_data &= ~PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT; + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write Copper Control Register failed", dev->name); + return ret; + } + + ret = phy_marvell_alaska_reset(dev); + if (ret) { + LOG_ERR("%s: configure PHY: reset PHY failed (1)", dev->name); + return ret; + } + + if ((dev_data->phy_id & PHY_MRVL_PHY_ID_MODEL_MASK) == PHY_MRVL_PHY_ID_MODEL_88E151X) { + /* + * 88E151x only: configure the system interface and media type + * (i.e. "RGMII to Copper", 0x0). On the 88E1111, this setting + * is configured using I/O pins on the device. + * Page 18, register address 20 = General Control Register 1, + * bits [2..0] = mode configuration + * Comp. datasheet table 129 "General Control Register 1" + * NOTICE: a change of this value requires a subsequent software + * reset command via the same register's bit [15]. + */ + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, + PHY_MRVL_GENERAL_CONTROL_1_PAGE); + if (ret) { + LOG_ERR("%s: configure PHY: write Page Switch Register failed", dev->name); + return ret; + } + + ret = phy_marvell_alaska_read(dev, PHY_MRVL_GENERAL_CONTROL_1_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read General Control Register 1 failed", + dev->name); + return ret; + } + phy_data &= ~(PHY_MRVL_MODE_CONFIG_MASK << PHY_MRVL_MODE_CONFIG_SHIFT); + ret = phy_marvell_alaska_write(dev, PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write General Control Register 1 failed", + dev->name); + return ret; + } + + /* + * [15] Mode Software Reset bit, affecting pages 6 and 18 + * Reset is performed immediately, bit [15] is self-clearing. + * This reset bit mirrors that in the Copper Control Register + * without the need for a prior register page switch. + */ + phy_data |= PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT; + ret = phy_marvell_alaska_write(dev, PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write General Control Register 1 failed", + dev->name); + return ret; + } + + /* Bit [15] reverts to 0 once the reset is complete. */ + while (((phy_data & PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT) != 0) && + (poll_cnt++ < 10)) { + ret = phy_marvell_alaska_read(dev, PHY_MRVL_GENERAL_CONTROL_1_REGISTER, + &phy_data); + } + if (poll_cnt == 10) { + LOG_ERR("%s: configure PHY: software-reset PHY completion timed out", + dev->name); + return -ETIMEDOUT; + } + + /* Revert to register page 0 */ + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, + PHY_MRVL_BASE_REGISTERS_PAGE); + if (ret) { + LOG_ERR("%s: configure PHY: write Page Switch Register failed", dev->name); + return ret; + } + } + + /* + * Configure MDIX + * 88E151x: Page 0, register address 16 = Copper specific control register 1, + * 88E1111: Page any, register address 16 = PHY specific control register, + * bits [6..5] = MDIO crossover mode. Comp. datasheet table 76. + * NOTICE: a change of this value requires a subsequent software + * reset command via Copper Control Register's bit [15]. + */ + + /* [6..5] 11 = Enable auto cross over detection */ + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_CONTROL_1_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read Copper spec. Control Register 1 failed", + dev->name); + return ret; + } + + phy_data &= ~(PHY_MRVL_MDIX_CONFIG_MASK << PHY_MRVL_MDIX_CONFIG_SHIFT); + phy_data |= (PHY_MRVL_MDIX_AUTO_CROSSOVER_ENABLE << PHY_MRVL_MDIX_CONFIG_SHIFT); + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_CONTROL_1_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write Copper spec. Control Register 1 failed", + dev->name); + return ret; + } + + /* Trigger a PHY Reset, affecting pages 0, 2, 3, 5, 7. */ + ret = phy_marvell_alaska_reset(dev); + if (ret) { + LOG_ERR("%s: configure PHY: reset PHY failed (2)", dev->name); + return ret; + } + + return 0; +} + +static int phy_marvell_alaska_cfg_link(const struct device *dev, enum phy_link_speed speeds) +{ + struct marvell_alaska_data *dev_data = dev->data; + + int ret = 0; + uint32_t phy_data; + uint32_t phy_data_gbit; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: configure PHY link: mutex lock error", dev->name); + return ret; + } + + /* Cancel monitoring delayable work during link re-configuration */ + k_work_cancel_delayable(&dev_data->phy_monitor_work); + + /* Reset PHY */ + ret = phy_marvell_alaska_reset(dev); + if (ret) { + goto out; + } + + /* Common configuration items */ + ret = phy_marvell_alaska_static_cfg(dev); + if (ret) { + goto out; + } + + /* + * Advertise the link speed from the device configuration & perform + * auto-negotiation. This process involves: + * + * Page 0, register address 4 = + * Copper Auto-Negotiation Advertisement Register, + * Page 0, register address 0 = + * Copper Control Register, bit [15] = Reset -> apply all changes + * made regarding advertisement, + * Page 0, register address 9 = + * 1000BASE-T Control Register (if link speed = 1GBit/s), + * Page 0, register address 1 = + * Copper Status Register, bit [5] = Copper Auto-Negotiation + * Complete. + * + * Comp. datasheet tables 68 & 73. + */ + + phy_data = MII_ADVERTISE_SEL_IEEE_802_3; + + /* + * Read 1000BASE-T Control Register, mask out GBit bits, + * modify & write this register. 10/100 advertisement data + * in the ANAR register is assembled from scratch. + */ + ret = phy_marvell_alaska_read(dev, PHY_MRVL_1000BASET_CONTROL_REGISTER, &phy_data_gbit); + if (ret) { + goto out; + } + phy_data_gbit &= ~PHY_MRVL_ADVERTISE_1000_FULL; + phy_data_gbit &= ~PHY_MRVL_ADVERTISE_1000_HALF; + + if (speeds & LINK_FULL_1000BASE) { + phy_data_gbit |= PHY_MRVL_ADVERTISE_1000_FULL; + } + if (speeds & LINK_HALF_1000BASE) { + phy_data_gbit |= PHY_MRVL_ADVERTISE_1000_HALF; + } + if (speeds & LINK_FULL_100BASE) { + phy_data |= MII_ADVERTISE_100_FULL; + } + if (speeds & LINK_HALF_100BASE) { + phy_data |= MII_ADVERTISE_100_HALF; + } + if (speeds & LINK_FULL_10BASE) { + phy_data |= MII_ADVERTISE_10_FULL; + } + if (speeds & LINK_HALF_10BASE) { + phy_data |= MII_ADVERTISE_10_HALF; + } + + LOG_DBG("%s: configure PHY link: 1000CTRL 0x%08X ANAR 0x%08X", dev->name, phy_data_gbit, + phy_data); + + ret = phy_marvell_alaska_write(dev, PHY_MRVL_1000BASET_CONTROL_REGISTER, phy_data_gbit); + if (ret) { + goto out; + } + ret = phy_marvell_alaska_write(dev, PHY_MRVL_COPPER_AUTONEG_ADV_REGISTER, phy_data); + if (ret) { + goto out; + } + + /* Start auto-negotiation */ + ret = phy_marvell_alaska_autonegotiate(dev); + if (ret) { + LOG_ERR("%s: configure PHY link: auto-negotiation failed", dev->name); + } + +out: + k_mutex_unlock(&dev_data->mutex); + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + + return ret; +} + +static int phy_marvell_alaska_get_link(const struct device *dev, struct phy_link_state *state) +{ + struct marvell_alaska_data *dev_data = dev->data; + + int ret; + uint32_t phy_data; + uint32_t speed; + bool fdx; + bool link_up; + + struct phy_link_state old_state = dev_data->state; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: get PHY link state: mutex lock error", dev->name); + return ret; + } + + ret = phy_marvell_alaska_read(dev, PHY_MRVL_COPPER_STATUS_1_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: get PHY link state: read Copper Specific Status Register 1 failed", + dev->name); + k_mutex_unlock(&dev_data->mutex); + return ret; + } + + /* + * Copper Specific Status Register 1 (88E15xx) / + * PHY Specific Status Register - Copper (88E1111): + * + * Link speed: [15 .. 14] + * 00b = 10 MBit/s + * 01b = 100 MBit/s + * 10b = 1 GBit/s + * Duplex: [13] + * 0b = half duplex + * 1b = full duplex + * Link State: [10] + * 0b = link down + * 1b = link up + */ + speed = (phy_data >> PHY_MRVL_LINK_SPEED_SHIFT) & PHY_MRVL_LINK_SPEED_MASK; + fdx = (phy_data & PHY_MRVL_LINK_DUPLEX_FDX) ? true : false; + link_up = (phy_data & PHY_MRVL_LINK_STATUS) ? true : false; + + if (speed == PHY_MRVL_LINK_SPEED_10MBIT) { + if (fdx) { + state->speed = LINK_FULL_10BASE; + } else { + state->speed = LINK_HALF_10BASE; + } + } else if (speed == PHY_MRVL_LINK_SPEED_100MBIT) { + if (fdx) { + state->speed = LINK_FULL_100BASE; + } else { + state->speed = LINK_HALF_100BASE; + } + } else if (speed == PHY_MRVL_LINK_SPEED_1GBIT) { + if (fdx) { + state->speed = LINK_FULL_1000BASE; + } else { + state->speed = LINK_HALF_1000BASE; + } + } + state->is_up = link_up; + + k_mutex_unlock(&dev_data->mutex); + + if (memcmp(&old_state, state, sizeof(struct phy_link_state)) != 0) { + LOG_DBG("%s: PHY link is %s", dev->name, state->is_up ? "up" : "down"); + if (state->is_up) { + LOG_DBG("%s: PHY configured for %s MBit/s %s", dev->name, + PHY_LINK_IS_SPEED_1000M(state->speed) ? "1000" + : PHY_LINK_IS_SPEED_100M(state->speed) ? "100" + : "10", + PHY_LINK_IS_FULL_DUPLEX(state->speed) ? "FDX" : "HDX"); + } + } + + return ret; +} + +static int phy_marvell_alaska_link_cb_set(const struct device *dev, phy_callback_t cb, + void *user_data) +{ + struct marvell_alaska_data *dev_data = dev->data; + + int ret = 0; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: set link state callback: mutex lock error", dev->name); + return ret; + } + + dev_data->cb = cb; + dev_data->cb_data = user_data; + + k_mutex_unlock(&dev_data->mutex); + + /* Initial state propagation to the newly registered callback function */ + phy_marvell_alaska_get_link(dev, &dev_data->state); + dev_data->cb(dev, &dev_data->state, dev_data->cb_data); + + return 0; +} + +static void phy_marvell_alaska_monitor_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct marvell_alaska_data *dev_data = + CONTAINER_OF(dwork, struct marvell_alaska_data, phy_monitor_work); + const struct device *dev = dev_data->dev; + + struct phy_link_state state = {}; + int ret = phy_marvell_alaska_get_link(dev, &state); + + if (ret == 0 && memcmp(&state, &dev_data->state, sizeof(struct phy_link_state)) != 0) { + memcpy(&dev_data->state, &state, sizeof(struct phy_link_state)); + if (dev_data->cb) { + dev_data->cb(dev, &dev_data->state, dev_data->cb_data); + } + } + + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); +} + +static int phy_marvell_alaska_init(const struct device *dev) +{ + const struct marvell_alaska_config *const dev_conf = dev->config; + struct marvell_alaska_data *dev_data = dev->data; + + int ret; + + dev_data->dev = dev; + + ret = k_mutex_init(&dev_data->mutex); + if (ret) { + LOG_ERR("%s: init PHY: initialize mutex failed", dev->name); + return ret; + } + + mdio_bus_enable(dev_conf->mdio_dev); + + k_work_init_delayable(&dev_data->phy_monitor_work, phy_marvell_alaska_monitor_work_handler); + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + + LOG_DBG("%s: init PHY: completed", dev->name); + + return 0; +} + +static DEVICE_API(ethphy, marvell_alaska_phy_api) = { + .get_link = phy_marvell_alaska_get_link, + .cfg_link = phy_marvell_alaska_cfg_link, + .link_cb_set = phy_marvell_alaska_link_cb_set, + .read = phy_marvell_alaska_read, + .write = phy_marvell_alaska_write, +}; + +#define PHY_MARVELL_ALASKA_DEV_CONFIG(n) \ + static const struct marvell_alaska_config marvell_alaska_##n##_config = { \ + .mdio_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .addr = DT_INST_REG_ADDR(n), \ + }; + +#define PHY_MARVELL_ALASKA_DEV_DATA(n) \ + static struct marvell_alaska_data marvell_alaska_##n##_data = { \ + .phy_id = 0, \ + }; + +#define PHY_MARVELL_ALASKA_DEV_INIT(n) \ + DEVICE_DT_INST_DEFINE(n, phy_marvell_alaska_init, NULL, &marvell_alaska_##n##_data, \ + &marvell_alaska_##n##_config, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ + &marvell_alaska_phy_api); + +#define PHY_MARVELL_ALASKA_INITIALIZE(n) \ + PHY_MARVELL_ALASKA_DEV_CONFIG(n); \ + PHY_MARVELL_ALASKA_DEV_DATA(n); \ + PHY_MARVELL_ALASKA_DEV_INIT(n); + +DT_INST_FOREACH_STATUS_OKAY(PHY_MARVELL_ALASKA_INITIALIZE) diff --git a/drivers/ethernet/phy/phy_ti_dp83822.c b/drivers/ethernet/phy/phy_ti_dp83822.c new file mode 100644 index 0000000000000..e3d7218b8544d --- /dev/null +++ b/drivers/ethernet/phy/phy_ti_dp83822.c @@ -0,0 +1,473 @@ +/* + * Copyright (c) 2021 Weidmueller Interface GmbH & Co. KG + * Copyright (c) 2025 Immo Birnbaum + * + * Definitions and procedures moved here from the former proprietary + * Xilinx GEM PHY driver (phy_xlnx_gem.c), adjusted to use the MDIO + * framework provided by Zephyr. The TI DP83825 driver was used as a + * template, which is (c) Bernhard Kraemer. + * + * Register IDs & procedures are based on the corresponding datasheet: + * https://www.ti.com/lit/ds/symlink/dp83822i.pdf + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include + +#define DT_DRV_COMPAT ti_dp83822 +#define LOG_MODULE_NAME phy_ti_dp83822 + +#define LOG_LEVEL CONFIG_PHY_LOG_LEVEL +#include +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#define PHY_TI_CONTROL_REGISTER_1 0x0009 +#define PHY_TI_PHY_STATUS_REGISTER 0x0010 +#define PHY_TI_MII_INTERRUPT_STATUS_REGISTER_1 0x0012 +#define PHY_TI_LED_CONTROL_REGISTER 0x0018 +#define PHY_TI_PHY_CONTROL_REGISTER 0x0019 + +#define PHY_TI_CR1_ROBUST_AUTO_MDIX_BIT BIT(5) + +#define PHY_TI_PHY_CONTROL_AUTO_MDIX_ENABLE_BIT BIT(15) +#define PHY_TI_PHY_CONTROL_FORCE_MDIX_BIT BIT(14) +#define PHY_TI_PHY_CONTROL_LED_CONFIG_LINK_ONLY_BIT BIT(5) + +#define PHY_TI_LED_CONTROL_BLINK_RATE_SHIFT 9 +#define PHY_TI_LED_CONTROL_BLINK_RATE_20HZ 0 +#define PHY_TI_LED_CONTROL_BLINK_RATE_10HZ 1 +#define PHY_TI_LED_CONTROL_BLINK_RATE_5HZ 2 +#define PHY_TI_LED_CONTROL_BLINK_RATE_2HZ 3 + +#define PHY_TI_PHY_STATUS_LINK_BIT BIT(0) +#define PHY_TI_PHY_STATUS_SPEED_BIT BIT(1) +#define PHY_TI_PHY_STATUS_DUPLEX_BIT BIT(2) + +struct ti_dp83822_config { + const struct device *mdio_dev; + uint8_t addr; +}; + +struct ti_dp83822_data { + const struct device *dev; + + struct phy_link_state state; + phy_callback_t cb; + void *cb_data; + + struct k_mutex mutex; + struct k_work_delayable phy_monitor_work; +}; + +static int phy_ti_dp83822_read(const struct device *dev, uint16_t reg_addr, uint32_t *data) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + + /* Make sure excessive bits 16-31 are reset */ + *data = 0U; + + return mdio_read(dev_conf->mdio_dev, dev_conf->addr, reg_addr, (uint16_t *)data); +} + +static int phy_ti_dp83822_write(const struct device *dev, uint16_t reg_addr, uint32_t data) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + + return mdio_write(dev_conf->mdio_dev, dev_conf->addr, reg_addr, (uint16_t)data); +} + +static int phy_ti_dp83822_reset(const struct device *dev) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + + int ret = 0; + uint32_t phy_data; + uint32_t poll_cnt = 0; + + ret = phy_ti_dp83822_read(dev, MII_BMCR, &phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: read BCMR failed", dev->name); + return ret; + } + + phy_data |= MII_BMCR_RESET; + ret = phy_ti_dp83822_write(dev, MII_BMCR, phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: write BCMR failed", dev->name); + return ret; + } + + while (((phy_data & MII_BMCR_RESET) != 0) && (poll_cnt++ < 10)) { + ret = phy_ti_dp83822_read(dev, MII_BMCR, &phy_data); + if (ret) { + LOG_ERR("%s: reset PHY: read BCMR (poll completion) failed", dev->name); + return ret; + } + } + if (poll_cnt == 10) { + LOG_ERR("%s: reset PHY: reset completion timed out", dev->name); + ret = -ETIMEDOUT; + } + + return ret; +} + +static int phy_ti_dp83822_autonegotiate(const struct device *dev) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + + int ret; + uint32_t bmcr; + + ret = phy_ti_dp83822_read(dev, MII_BMCR, &bmcr); + if (ret) { + LOG_ERR("%s: trigger auto-neg: read BMCR failed", dev->name); + return ret; + } + + LOG_DBG("%s: triggering PHY link auto-negotiation", dev->name); + bmcr |= MII_BMCR_AUTONEG_ENABLE | MII_BMCR_AUTONEG_RESTART; + bmcr &= ~MII_BMCR_ISOLATE; + + ret = phy_ti_dp83822_write(dev, MII_BMCR, bmcr); + if (ret) { + LOG_ERR("%s: trigger auto-neg: write BMCR failed", dev->name); + return ret; + } + + return 0; +} + +static int phy_ti_dp83822_static_cfg(const struct device *dev) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + + int ret; + uint32_t phy_data; + uint32_t phy_id; + + /* Read PHY ID */ + ret = phy_ti_dp83822_read(dev, MII_PHYID1R, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read PHYID1R failed", dev->name); + return ret; + } + phy_id = ((phy_data << 16) & 0xFFFF0000); + ret = phy_ti_dp83822_read(dev, MII_PHYID2R, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read PHYID2R failed", dev->name); + return ret; + } + phy_id |= (phy_data & 0x0000FFFF); + + if (phy_id == 0 || phy_id == 0xFFFFFFFF) { + LOG_ERR("%s: configure PHY: no reply from PHY while reading PHY ID", dev->name); + return -EIO; + } + + LOG_DBG("%s: configure PHY: read PHY ID 0x%08X", dev->name, phy_id); + + /* Enable auto-negotiation */ + ret = phy_ti_dp83822_read(dev, MII_BMCR, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read BMCR failed", dev->name); + return ret; + } + phy_data |= MII_BMCR_AUTONEG_ENABLE; + ret = phy_ti_dp83822_write(dev, MII_BMCR, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write BMCR failed", dev->name); + return ret; + } + + /* Robust Auto MDIX */ + ret = phy_ti_dp83822_read(dev, PHY_TI_CONTROL_REGISTER_1, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read CR1 failed", dev->name); + return ret; + } + phy_data |= PHY_TI_CR1_ROBUST_AUTO_MDIX_BIT; + ret = phy_ti_dp83822_write(dev, PHY_TI_CONTROL_REGISTER_1, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write CR1 failed", dev->name); + return ret; + } + + ret = phy_ti_dp83822_read(dev, PHY_TI_PHY_CONTROL_REGISTER, &phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: read PHYCR failed", dev->name); + return ret; + } + /* Auto MDIX enable */ + phy_data |= PHY_TI_PHY_CONTROL_AUTO_MDIX_ENABLE_BIT; + /* Link LED shall only indicate link up or down, no RX/TX activity */ + phy_data |= PHY_TI_PHY_CONTROL_LED_CONFIG_LINK_ONLY_BIT; + /* Force MDIX disable */ + phy_data &= ~PHY_TI_PHY_CONTROL_FORCE_MDIX_BIT; + ret = phy_ti_dp83822_write(dev, PHY_TI_PHY_CONTROL_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write PHYCR failed", dev->name); + return ret; + } + + /* Set blink rate to 5 Hz */ + phy_data = (PHY_TI_LED_CONTROL_BLINK_RATE_5HZ << PHY_TI_LED_CONTROL_BLINK_RATE_SHIFT); + ret = phy_ti_dp83822_write(dev, PHY_TI_LED_CONTROL_REGISTER, phy_data); + if (ret) { + LOG_ERR("%s: configure PHY: write LEDCR failed", dev->name); + } + + return ret; +} + +static int phy_ti_dp83822_cfg_link(const struct device *dev, enum phy_link_speed speeds) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + struct ti_dp83822_data *dev_data = dev->data; + + int ret = 0; + uint32_t anar = MII_ADVERTISE_SEL_IEEE_802_3; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: configure PHY link: mutex lock error", dev->name); + return ret; + } + + /* Cancel monitoring delayable work during link re-configuration */ + k_work_cancel_delayable(&dev_data->phy_monitor_work); + + /* Reset PHY */ + ret = phy_ti_dp83822_reset(dev); + if (ret) { + goto out; + } + + /* Common configuration items */ + ret = phy_ti_dp83822_static_cfg(dev); + if (ret) { + goto out; + } + + /* Configure Auto-Negotiation Advertisement Register (ANAR) */ + ret = phy_ti_dp83822_read(dev, MII_ANAR, &anar); + if (ret) { + LOG_ERR("%s: configure PHY link: read ANAR failed", dev->name); + goto out; + } + + /* Set link configuration(s) to be advertised in ANAR */ + if (speeds & LINK_FULL_100BASE) { + anar |= MII_ADVERTISE_100_FULL; + } else { + anar &= ~MII_ADVERTISE_100_FULL; + } + + if (speeds & LINK_HALF_100BASE) { + anar |= MII_ADVERTISE_100_HALF; + } else { + anar &= ~MII_ADVERTISE_100_HALF; + } + + if (speeds & LINK_FULL_10BASE) { + anar |= MII_ADVERTISE_10_FULL; + } else { + anar &= ~MII_ADVERTISE_10_FULL; + } + + if (speeds & LINK_HALF_10BASE) { + anar |= MII_ADVERTISE_10_HALF; + } else { + anar &= ~MII_ADVERTISE_10_HALF; + } + + /* Write assembled ANAR contents */ + ret = phy_ti_dp83822_write(dev, MII_ANAR, anar); + if (ret) { + LOG_ERR("%s: configure PHY link: write ANAR failed", dev->name); + goto out; + } + + /* Start auto-negotiation */ + ret = phy_ti_dp83822_autonegotiate(dev); + if (ret) { + LOG_ERR("%s: configure PHY link: auto-negotiation failed", dev->name); + } + +out: + k_mutex_unlock(&dev_data->mutex); + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + + return ret; +} + +static int phy_ti_dp83822_get_link(const struct device *dev, struct phy_link_state *state) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + struct ti_dp83822_data *dev_data = dev->data; + + int ret; + uint32_t physts; + + struct phy_link_state old_state = dev_data->state; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: get PHY link state: mutex lock error", dev->name); + return ret; + } + + /* Get link state from PHYSTS */ + ret = phy_ti_dp83822_read(dev, PHY_TI_PHY_STATUS_REGISTER, &physts); + if (ret) { + LOG_ERR("%s: get PHY link state: read PHYSTS failed", dev->name); + k_mutex_unlock(&dev_data->mutex); + return ret; + } + + /* + * Get link status from PHYSTS: + * [0] Link: 1 = up, 0 = down + * (Mirrored from BMSR) + */ + state->is_up = physts & PHY_TI_PHY_STATUS_LINK_BIT; + if (!state->is_up) { + k_mutex_unlock(&dev_data->mutex); + goto result; + } + + /* + * Get link speed and duplex from PHYSTS: + * [2] Duplex: 1 = full, 0 = half + * [1] Speed: 1 = 100 MBit/s, 0 = 10 MBit/s + */ + if (physts & PHY_TI_PHY_STATUS_DUPLEX_BIT) { + if (physts & PHY_TI_PHY_STATUS_SPEED_BIT) { + state->speed = LINK_FULL_100BASE; + } else { + state->speed = LINK_HALF_100BASE; + } + } else { + if (physts & PHY_TI_PHY_STATUS_SPEED_BIT) { + state->speed = LINK_FULL_10BASE; + } else { + state->speed = LINK_HALF_10BASE; + } + } + + k_mutex_unlock(&dev_data->mutex); + +result: + if (memcmp(&old_state, state, sizeof(struct phy_link_state)) != 0) { + LOG_DBG("%s: PHY link is %s", dev->name, state->is_up ? "up" : "down"); + if (state->is_up) { + LOG_DBG("%s: PHY configured for %s MBit/s %s", dev->name, + PHY_LINK_IS_SPEED_100M(state->speed) ? "100" : "10", + PHY_LINK_IS_FULL_DUPLEX(state->speed) ? "FDX" : "HDX"); + } + } + + return ret; +} + +static int phy_ti_dp83822_link_cb_set(const struct device *dev, phy_callback_t cb, void *user_data) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + struct ti_dp83822_data *dev_data = dev->data; + + int ret = 0; + + ret = k_mutex_lock(&dev_data->mutex, K_FOREVER); + if (ret) { + LOG_ERR("%s: set link state callback: mutex lock error", dev->name); + return ret; + } + + dev_data->cb = cb; + dev_data->cb_data = user_data; + + k_mutex_unlock(&dev_data->mutex); + + /* Initial state propagation to the newly registered callback function */ + phy_ti_dp83822_get_link(dev, &dev_data->state); + dev_data->cb(dev, &dev_data->state, dev_data->cb_data); + + return 0; +} + +static void phy_ti_dp83822_monitor_work_handler(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct ti_dp83822_data *dev_data = + CONTAINER_OF(dwork, struct ti_dp83822_data, phy_monitor_work); + const struct device *dev = dev_data->dev; + + struct phy_link_state state = {}; + int ret = phy_ti_dp83822_get_link(dev, &state); + + if (ret == 0 && memcmp(&state, &dev_data->state, sizeof(struct phy_link_state)) != 0) { + memcpy(&dev_data->state, &state, sizeof(struct phy_link_state)); + if (dev_data->cb) { + dev_data->cb(dev, &dev_data->state, dev_data->cb_data); + } + } + + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); +} + +static int phy_ti_dp83822_init(const struct device *dev) +{ + const struct ti_dp83822_config *const dev_conf = dev->config; + struct ti_dp83822_data *dev_data = dev->data; + + int ret; + + dev_data->dev = dev; + + ret = k_mutex_init(&dev_data->mutex); + if (ret) { + LOG_ERR("%s: init PHY: initialize mutex failed", dev->name); + return ret; + } + + mdio_bus_enable(dev_conf->mdio_dev); + + k_work_init_delayable(&dev_data->phy_monitor_work, phy_ti_dp83822_monitor_work_handler); + k_work_reschedule(&dev_data->phy_monitor_work, K_MSEC(CONFIG_PHY_MONITOR_PERIOD)); + + LOG_DBG("%s: init PHY: completed", dev->name); + + return 0; +} + +static DEVICE_API(ethphy, ti_dp83822_phy_api) = { + .get_link = phy_ti_dp83822_get_link, + .cfg_link = phy_ti_dp83822_cfg_link, + .link_cb_set = phy_ti_dp83822_link_cb_set, + .read = phy_ti_dp83822_read, + .write = phy_ti_dp83822_write, +}; + +#define PHY_TI_DP83822_DEV_CONFIG(n) \ + static const struct ti_dp83822_config ti_dp83822_##n##_config = { \ + .mdio_dev = DEVICE_DT_GET(DT_INST_PARENT(n)), \ + .addr = DT_INST_REG_ADDR(n), \ + }; + +#define PHY_TI_DP83822_DEV_DATA(n) static struct ti_dp83822_data ti_dp83822_##n##_data; + +#define PHY_TI_DP83822_DEV_INIT(n) \ + DEVICE_DT_INST_DEFINE(n, phy_ti_dp83822_init, NULL, &ti_dp83822_##n##_data, \ + &ti_dp83822_##n##_config, POST_KERNEL, CONFIG_PHY_INIT_PRIORITY, \ + &ti_dp83822_phy_api); + +#define PHY_TI_DP83822_INITIALIZE(n) \ + PHY_TI_DP83822_DEV_CONFIG(n); \ + PHY_TI_DP83822_DEV_DATA(n); \ + PHY_TI_DP83822_DEV_INIT(n); + +DT_INST_FOREACH_STATUS_OKAY(PHY_TI_DP83822_INITIALIZE) diff --git a/drivers/ethernet/phy_xlnx_gem.c b/drivers/ethernet/phy_xlnx_gem.c deleted file mode 100644 index 126626a0f25ac..0000000000000 --- a/drivers/ethernet/phy_xlnx_gem.c +++ /dev/null @@ -1,980 +0,0 @@ -/* - * Xilinx Processor System Gigabit Ethernet controller (GEM) driver - * - * PHY management interface implementation - * Models currently supported: - * - Marvell Alaska 88E1111 (QEMU simulated PHY) - * - Marvell Alaska 88E1510/88E1518/88E1512/88E1514 (Zedboard) - * - Texas Instruments TLK105 - * - Texas Instruments DP83822 - * - * Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG - * SPDX-License-Identifier: Apache-2.0 - */ - -#include -#include - -#include "eth_xlnx_gem_priv.h" - -#define LOG_MODULE_NAME phy_xlnx_gem -#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL -#include -LOG_MODULE_REGISTER(LOG_MODULE_NAME); - -/* Basic MDIO read / write functions for PHY access */ - -/** - * @brief Read PHY data via the MDIO interface - * Reads data from a PHY attached to the respective GEM's MDIO interface - * - * @param base_addr Base address of the GEM's register space - * @param phy_addr MDIO address of the PHY to be accessed - * @param reg_addr Index of the PHY register to be read - * @return 16-bit data word received from the PHY - */ -static uint16_t phy_xlnx_gem_mdio_read( - uint32_t base_addr, uint8_t phy_addr, - uint8_t reg_addr) -{ - uint32_t reg_val; - uint32_t poll_cnt = 0; - - /* - * MDIO read operation as described in Zynq-7000 TRM, - * chapter 16.3.4, p. 517. - */ - - /* - * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the - * current command. - */ - do { - if (poll_cnt++ > 0) { - k_busy_wait(100); - } - reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET); - } while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10); - if (poll_cnt == 10) { - LOG_ERR("GEM@0x%08X read from PHY address %hhu, " - "register address %hhu timed out", - base_addr, phy_addr, reg_addr); - return 0; - } - - /* Assemble & write the read command to the gem.phy_maint register */ - - /* Set the bits constant for any operation */ - reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS; - /* Indicate a read operation */ - reg_val |= ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT; - /* PHY address */ - reg_val |= (((uint32_t)phy_addr & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) << - ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT); - /* Register address */ - reg_val |= (((uint32_t)reg_addr & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) << - ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT); - - sys_write32(reg_val, base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); - - /* - * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command - * completed. - */ - poll_cnt = 0; - do { - if (poll_cnt++ > 0) { - k_busy_wait(100); - } - reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET); - } while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10); - if (poll_cnt == 10) { - LOG_ERR("GEM@0x%08X read from PHY address %hhu, " - "register address %hhu timed out", - base_addr, phy_addr, reg_addr); - return 0; - } - - /* - * Read the data returned by the PHY -> lower 16 bits of the PHY main- - * tenance register - */ - reg_val = sys_read32(base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); - return (uint16_t)reg_val; -} - -/** - * @brief Writes PHY data via the MDIO interface - * Writes data to a PHY attached to the respective GEM's MDIO interface - * - * @param base_addr Base address of the GEM's register space - * @param phy_addr MDIO address of the PHY to be accessed - * @param reg_addr Index of the PHY register to be written to - * @param value 16-bit data word to be written to the target register - */ -static void phy_xlnx_gem_mdio_write( - uint32_t base_addr, uint8_t phy_addr, - uint8_t reg_addr, uint16_t value) -{ - uint32_t reg_val; - uint32_t poll_cnt = 0; - - /* - * MDIO write operation as described in Zynq-7000 TRM, - * chapter 16.3.4, p. 517. - */ - - /* - * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the - * current command. - */ - do { - if (poll_cnt++ > 0) { - k_busy_wait(100); - } - reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET); - } while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10); - if (poll_cnt == 10) { - LOG_ERR("GEM@0x%08X write to PHY address %hhu, " - "register address %hhu timed out", - base_addr, phy_addr, reg_addr); - return; - } - - /* Assemble & write the read command to the gem.phy_maint register */ - - /* Set the bits constant for any operation */ - reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS; - /* Indicate a read operation */ - reg_val |= ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT; - /* PHY address */ - reg_val |= (((uint32_t)phy_addr & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) << - ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT); - /* Register address */ - reg_val |= (((uint32_t)reg_addr & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) << - ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT); - /* 16 bits of data for the destination register */ - reg_val |= ((uint32_t)value & ETH_XLNX_GEM_PHY_MAINT_DATA_MASK); - - sys_write32(reg_val, base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); - - /* - * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command - * completed. - */ - poll_cnt = 0; - do { - if (poll_cnt++ > 0) { - k_busy_wait(100); - } - reg_val = sys_read32(base_addr + ETH_XLNX_GEM_NWSR_OFFSET); - } while ((reg_val & ETH_XLNX_GEM_MDIO_IDLE_BIT) == 0 && poll_cnt < 10); - if (poll_cnt == 10) { - LOG_ERR("GEM@0x%08X write to PHY address %hhu, " - "register address %hhu timed out", - base_addr, phy_addr, reg_addr); - } -} - -/* - * Vendor-specific PHY management functions for: - * Marvell Alaska 88E1111 (QEMU simulated PHY) - * Marvell Alaska 88E1510/88E1518/88E1512/88E1514 (Zedboard) - * Register IDs & procedures are based on the corresponding datasheets: - * https://www.marvell.com/content/dam/marvell/en/public-collateral/transceivers/marvell-phys-transceivers-alaska-88e1111-datasheet.pdf - * https://www.marvell.com/content/dam/marvell/en/public-collateral/transceivers/marvell-phys-transceivers-alaska-88e151x-datasheet.pdf - * - * NOTICE: Unless indicated otherwise, page/table source references refer to - * the 88E151x datasheet. - */ - -/** - * @brief Marvell Alaska PHY reset function - * Reset function for the Marvell Alaska PHY series - * - * @param dev Pointer to the device data - */ -static void phy_xlnx_gem_marvell_alaska_reset(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - uint32_t retries = 0; - - /* - * Page 0, register address 0 = Copper control register, - * bit [15] = PHY reset. Register 0/0 access is R/M/W. Comp. - * datasheet chapter 2.6 and table 64 "Copper Control Register". - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER); - phy_data |= PHY_MRVL_COPPER_CONTROL_RESET_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); - - /* Bit [15] reverts to 0 once the reset is complete. */ - while (((phy_data & PHY_MRVL_COPPER_CONTROL_RESET_BIT) != 0) && (retries++ < 10)) { - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER); - } - if (retries == 10) { - LOG_ERR("%s reset PHY address %hhu (Marvell Alaska) timed out", - dev->name, dev_data->phy_addr); - } -} - -/** - * @brief Marvell Alaska PHY configuration function - * Configuration function for the Marvell Alaska PHY series - * - * @param dev Pointer to the device data - */ -static void phy_xlnx_gem_marvell_alaska_cfg(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - uint16_t phy_data_gbit; - uint32_t retries = 0; - - /* - * Page 0, register address 0 = Copper control register, - * bit [12] = auto-negotiation enable bit is to be cleared - * for now, afterwards, trigger a PHY reset. - * Register 0/0 access is R/M/W. Comp. datasheet chapter 2.6 - * and table 64 "Copper Control Register". - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER); - phy_data &= ~PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); - phy_xlnx_gem_marvell_alaska_reset(dev); - - if ((dev_data->phy_id & PHY_MRVL_PHY_ID_MODEL_MASK) == - PHY_MRVL_PHY_ID_MODEL_88E151X) { - /* - * 88E151x only: configure the system interface and media type - * (i.e. "RGMII to Copper", 0x0). On the 88E1111, this setting - * is configured using I/O pins on the device. - * TODO: Make this value configurable via KConfig or DT? - * Page 18, register address 20 = General Control Register 1, - * bits [2..0] = mode configuration - * Comp. datasheet table 129 "General Control Register 1" - * NOTICE: a change of this value requires a subsequent software - * reset command via the same register's bit [15]. - */ - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, - PHY_MRVL_GENERAL_CONTROL_1_PAGE); - - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_GENERAL_CONTROL_1_REGISTER); - phy_data &= ~(PHY_MRVL_MODE_CONFIG_MASK << PHY_MRVL_MODE_CONFIG_SHIFT); - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data); - - /* - * [15] Mode Software Reset bit, affecting pages 6 and 18 - * Reset is performed immediately, bit [15] is self-clearing. - * This reset bit is not to be confused with the actual PHY - * reset in register 0/0! - */ - phy_data |= PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_GENERAL_CONTROL_1_REGISTER, phy_data); - - /* Bit [15] reverts to 0 once the reset is complete. */ - while (((phy_data & PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT) != 0) && - (retries++ < 10)) { - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, - dev_data->phy_addr, - PHY_MRVL_GENERAL_CONTROL_1_REGISTER); - } - if (retries == 10) { - LOG_ERR("%s configure PHY address %hhu (Marvell Alaska) timed out", - dev->name, dev_data->phy_addr); - return; - } - - /* Revert to register page 0 */ - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER, - PHY_MRVL_BASE_REGISTERS_PAGE); - } - - /* - * Configure MDIX - * TODO: Make this value configurable via KConfig or DT? - * 88E151x: Page 0, register address 16 = Copper specific control register 1, - * 88E1111: Page any, register address 16 = PHY specific control register, - * bits [6..5] = MDIO crossover mode. Comp. datasheet table 76. - * NOTICE: a change of this value requires a subsequent software - * reset command via Copper Control Register's bit [15]. - */ - - /* [6..5] 11 = Enable auto cross over detection */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_1_REGISTER); - phy_data &= ~(PHY_MRVL_MDIX_CONFIG_MASK << PHY_MRVL_MDIX_CONFIG_SHIFT); - phy_data |= (PHY_MRVL_MDIX_AUTO_CROSSOVER_ENABLE << PHY_MRVL_MDIX_CONFIG_SHIFT); - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_1_REGISTER, phy_data); - - /* - * Configure the Copper Specific Interrupt Enable Register - * (88E151x) / Interrupt Enable Register (88E1111). - * The interrupt status register provides a convenient way to - * detect relevant state changes, also, PHY management could - * eventually be changed from polling to interrupt-driven. - * There's just one big catch: at least on the Zedboard, the - * PHY interrupt line isn't wired up, therefore, the GEM can - * never trigger a PHY interrupt. Still, the PHY interrupts - * are configured & enabled in order to obtain all relevant - * status data from a single source. - * - * -> all bits contained herein will be retained during the - * upcoming software reset operation. - * Page 0, register address 18 = (Copper Specific) Interrupt - * Enable Register, - * bit [14] = Speed changed interrupt enable, - * bit [13] = Duplex changed interrupt enable, - * bit [11] = Auto-negotiation completed interrupt enable, - * bit [10] = Link status changed interrupt enable. - * Comp. datasheet table 78 - */ - phy_data = PHY_MRVL_COPPER_SPEED_CHANGED_INT_BIT | - PHY_MRVL_COPPER_DUPLEX_CHANGED_INT_BIT | - PHY_MRVL_COPPER_AUTONEG_COMPLETED_INT_BIT | - PHY_MRVL_COPPER_LINK_STATUS_CHANGED_INT_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_INT_ENABLE_REGISTER, phy_data); - - /* Trigger a PHY Reset, affecting pages 0, 2, 3, 5, 7. */ - phy_xlnx_gem_marvell_alaska_reset(dev); - - /* - * Clear the interrupt status register before advertising the - * supported link speed(s). - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_INT_STATUS_REGISTER); - - /* - * Set which link speeds and duplex modes shall be advertised during - * auto-negotiation, then re-enable auto-negotiation. PHY link speed - * advertisement configuration as described in Zynq-7000 TRM, chapter - * 16.3.4, p. 517. - */ - - /* - * Advertise the link speed from the device configuration & perform - * auto-negotiation. This process involves: - * - * Page 0, register address 4 = - * Copper Auto-Negotiation Advertisement Register, - * Page 0, register address 0 = - * Copper Control Register, bit [15] = Reset -> apply all changes - * made regarding advertisement, - * Page 0, register address 9 = - * 1000BASE-T Control Register (if link speed = 1GBit/s), - * Page 0, register address 1 = - * Copper Status Register, bit [5] = Copper Auto-Negotiation - * Complete. - * - * Comp. datasheet tables 68 & 73. - */ - - /* - * 88E151x only: - * Register 4, bits [4..0] = Selector field, 00001 = 802.3. Those bits - * are reserved in other Marvell PHYs. - */ - if ((dev_data->phy_id & PHY_MRVL_PHY_ID_MODEL_MASK) == - PHY_MRVL_PHY_ID_MODEL_88E151X) { - phy_data = PHY_MRVL_ADV_SELECTOR_802_3; - } else { - phy_data = 0x0000; - } - - /* - * Clear the 1 GBit/s FDX/HDX advertisement bits from reg. 9's current - * contents in case we're going to advertise anything below 1 GBit/s - * as maximum / nominal link speed. - */ - phy_data_gbit = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_1000BASET_CONTROL_REGISTER); - phy_data_gbit &= ~PHY_MRVL_ADV_1000BASET_FDX_BIT; - phy_data_gbit &= ~PHY_MRVL_ADV_1000BASET_HDX_BIT; - - if (dev_conf->enable_fdx) { - if (dev_conf->max_link_speed == LINK_1GBIT) { - /* Advertise 1 GBit/s, full duplex */ - phy_data_gbit |= PHY_MRVL_ADV_1000BASET_FDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 100 MBit/s, full duplex */ - phy_data |= PHY_MRVL_ADV_100BASET_FDX_BIT; - /* + 10 MBit/s, full duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_100MBIT) { - /* Advertise 100 MBit/s, full duplex */ - phy_data |= PHY_MRVL_ADV_100BASET_FDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 10 MBit/s, full duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_10MBIT) { - /* Advertise 10 MBit/s, full duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_FDX_BIT; - } - } else { - if (dev_conf->max_link_speed == LINK_1GBIT) { - /* Advertise 1 GBit/s, half duplex */ - phy_data_gbit = PHY_MRVL_ADV_1000BASET_HDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 100 MBit/s, half duplex */ - phy_data |= PHY_MRVL_ADV_100BASET_HDX_BIT; - /* + 10 MBit/s, half duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_100MBIT) { - /* Advertise 100 MBit/s, half duplex */ - phy_data |= PHY_MRVL_ADV_100BASET_HDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 10 MBit/s, half duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_10MBIT) { - /* Advertise 10 MBit/s, half duplex */ - phy_data |= PHY_MRVL_ADV_10BASET_HDX_BIT; - } - } - - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_1000BASET_CONTROL_REGISTER, phy_data_gbit); - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_AUTONEG_ADV_REGISTER, phy_data); - - /* - * Trigger a PHY reset, affecting pages 0, 2, 3, 5, 7. - * Afterwards, set the auto-negotiation enable bit [12] in the - * Copper Control Register. - */ - phy_xlnx_gem_marvell_alaska_reset(dev); - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER); - phy_data |= PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_CONTROL_REGISTER, phy_data); - - /* - * Set the link speed to 'link down' for now, once auto-negotiation - * is complete, the result will be handled by the system work queue. - */ - dev_data->eff_link_speed = LINK_DOWN; -} - -/** - * @brief Marvell Alaska PHY status change polling function - * Status change polling function for the Marvell Alaska PHY series - * - * @param dev Pointer to the device data - * @return A set of bits indicating whether one or more of the following - * events has occurred: auto-negotiation completed, link state - * changed, link speed changed. - */ -static uint16_t phy_xlnx_gem_marvell_alaska_poll_sc(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - uint16_t phy_status = 0; - - /* - * PHY status change detection is implemented by reading the - * interrupt status register. - * Page 0, register address 19 = Copper Interrupt Status Register - * bit [14] = Speed changed interrupt, - * bit [13] = Duplex changed interrupt, - * bit [11] = Auto-negotiation completed interrupt, - * bit [10] = Link status changed interrupt. - * Comp. datasheet table 79 - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_INT_STATUS_REGISTER); - - if ((phy_data & PHY_MRVL_COPPER_AUTONEG_COMPLETED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE; - } - if (((phy_data & PHY_MRVL_COPPER_DUPLEX_CHANGED_INT_BIT) != 0) || - ((phy_data & PHY_MRVL_COPPER_LINK_STATUS_CHANGED_INT_BIT) != 0)) { - phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED; - } - if ((phy_data & PHY_MRVL_COPPER_SPEED_CHANGED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED; - } - - /* - * Clear the status register, preserve reserved bit [3] as indicated - * by the datasheet - */ - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_INT_STATUS_REGISTER, (phy_data & 0x8)); - - return phy_status; -} - -/** - * @brief Marvell Alaska PHY link status polling function - * Link status polling function for the Marvell Alaska PHY series - * - * @param dev Pointer to the device data - * @return 1 if the PHY indicates link up, 0 if the link is down - */ -static uint8_t phy_xlnx_gem_marvell_alaska_poll_lsts(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - - /* - * Current link status is obtained from: - * Page 0, register address 1 = Copper Status Register - * bit [2] = Copper Link Status - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_STATUS_REGISTER); - - return ((phy_data >> PHY_MRVL_COPPER_LINK_STATUS_BIT_SHIFT) & 0x0001); -} - -/** - * @brief Marvell Alaska PHY link speed polling function - * Link speed polling function for the Marvell Alaska PHY series - * - * @param dev Pointer to the device data - * @return Enum containing the current link speed reported by the PHY - */ -static enum eth_xlnx_link_speed phy_xlnx_gem_marvell_alaska_poll_lspd( - const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - enum eth_xlnx_link_speed link_speed; - uint16_t phy_data; - - /* - * Current link speed is obtained from: - * Page 0, register address 17 = Copper Specific Status Register 1 - * bits [15 .. 14] = Speed. - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_MRVL_COPPER_STATUS_1_REGISTER); - phy_data >>= PHY_MRVL_LINK_SPEED_SHIFT; - phy_data &= PHY_MRVL_LINK_SPEED_MASK; - - /* - * Link speed bit masks: comp. datasheet, table 77 @ description - * of the 'Speed' bits. - */ - switch (phy_data) { - case PHY_MRVL_LINK_SPEED_10MBIT: - link_speed = LINK_10MBIT; - break; - case PHY_MRVL_LINK_SPEED_100MBIT: - link_speed = LINK_100MBIT; - break; - case PHY_MRVL_LINK_SPEED_1GBIT: - link_speed = LINK_1GBIT; - break; - default: - link_speed = LINK_DOWN; - break; - }; - - return link_speed; -} - -/* - * Vendor-specific PHY management functions for: - * Texas Instruments TLK105 - * Texas Instruments DP83822 - * with the DP83822 being the successor to the deprecated TLK105. - * Register IDs & procedures are based on the corresponding datasheets: - * https://www.ti.com/lit/gpn/tlk105 - * https://www.ti.com/lit/gpn/dp83822i - */ - -/** - * @brief TI TLK105 & DP83822 PHY reset function - * Reset function for the TI TLK105 & DP83822 PHYs - * - * @param dev Pointer to the device data - */ -static void phy_xlnx_gem_ti_dp83822_reset(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - uint32_t retries = 0; - - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_CONTROL_REGISTER); - phy_data |= PHY_TI_BASIC_MODE_CONTROL_RESET_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_CONTROL_REGISTER, phy_data); - - while (((phy_data & PHY_TI_BASIC_MODE_CONTROL_RESET_BIT) != 0) && (retries++ < 10)) { - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_CONTROL_REGISTER); - } - if (retries == 10) { - LOG_ERR("%s reset PHY address %hhu (TI TLK105/DP83822) timed out", - dev->name, dev_data->phy_addr); - } -} - -/** - * @brief TI TLK105 & DP83822 PHY configuration function - * Configuration function for the TI TLK105 & DP83822 PHYs - * - * @param dev Pointer to the device data - */ -static void phy_xlnx_gem_ti_dp83822_cfg(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data = PHY_TI_ADV_SELECTOR_802_3; - - /* Configure link advertisement */ - if (dev_conf->enable_fdx) { - if (dev_conf->max_link_speed == LINK_100MBIT) { - /* Advertise 100BASE-TX, full duplex */ - phy_data |= PHY_TI_ADV_100BASET_FDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 10BASE-TX, full duplex */ - phy_data |= PHY_TI_ADV_10BASET_FDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_10MBIT) { - /* Advertise 10BASE-TX, full duplex */ - phy_data |= PHY_TI_ADV_10BASET_FDX_BIT; - } - } else { - if (dev_conf->max_link_speed == LINK_100MBIT) { - /* Advertise 100BASE-TX, half duplex */ - phy_data |= PHY_TI_ADV_100BASET_HDX_BIT; - if (dev_conf->phy_advertise_lower) { - /* + 10BASE-TX, half duplex */ - phy_data |= PHY_TI_ADV_10BASET_HDX_BIT; - } - } else if (dev_conf->max_link_speed == LINK_10MBIT) { - /* Advertise 10BASE-TX, half duplex */ - phy_data |= PHY_TI_ADV_10BASET_HDX_BIT; - } - } - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_AUTONEG_ADV_REGISTER, phy_data); - - /* Enable auto-negotiation */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_CONTROL_REGISTER); - phy_data |= PHY_TI_BASIC_MODE_CONTROL_AUTONEG_ENABLE_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_CONTROL_REGISTER, phy_data); - - /* Robust Auto MDIX */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_CONTROL_REGISTER_1); - phy_data |= PHY_TI_CR1_ROBUST_AUTO_MDIX_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_CONTROL_REGISTER_1, phy_data); - - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_PHY_CONTROL_REGISTER); - /* Auto MDIX enable */ - phy_data |= PHY_TI_PHY_CONTROL_AUTO_MDIX_ENABLE_BIT; - /* Link LED shall only indicate link up or down, no RX/TX activity */ - phy_data |= PHY_TI_PHY_CONTROL_LED_CONFIG_LINK_ONLY_BIT; - /* Force MDIX disable */ - phy_data &= ~PHY_TI_PHY_CONTROL_FORCE_MDIX_BIT; - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_PHY_CONTROL_REGISTER, phy_data); - - /* Set blink rate to 5 Hz */ - phy_data = (PHY_TI_LED_CONTROL_BLINK_RATE_5HZ << - PHY_TI_LED_CONTROL_BLINK_RATE_SHIFT); - phy_xlnx_gem_mdio_write(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_LED_CONTROL_REGISTER, phy_data); - - /* - * Set the link speed to 'link down' for now, once auto-negotiation - * is complete, the result will be handled by the system work queue. - */ - dev_data->eff_link_speed = LINK_DOWN; -} - -/** - * @brief TI TLK105 & DP83822 PHY status change polling function - * Status change polling function for the TI TLK105 & DP83822 PHYs - * - * @param dev Pointer to the device data - * @return A set of bits indicating whether one or more of the following - * events has occurred: auto-negotiation completed, link state - * changed, link speed changed. - */ -static uint16_t phy_xlnx_gem_ti_dp83822_poll_sc(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - uint16_t phy_status = 0; - - /* - * The relevant status bits are obtained from the MII Interrupt - * Status Register 1. The upper byte of the register's data word - * contains the status bits which are set regardless of whether - * the corresponding interrupt enable bits are set in the lower - * byte or not (comp. TLK105 documentation, chapter 8.1.16). - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_MII_INTERRUPT_STATUS_REGISTER_1); - - if ((phy_data & PHY_TI_AUTONEG_COMPLETED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE; - } - if ((phy_data & PHY_TI_DUPLEX_CHANGED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED; - } - if ((phy_data & PHY_TI_LINK_STATUS_CHANGED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED; - } - if ((phy_data & PHY_TI_SPEED_CHANGED_INT_BIT) != 0) { - phy_status |= PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED; - } - - return phy_status; -} - -/** - * @brief TI TLK105 & DP83822 PHY link status polling function - * Link status polling function for the TI TLK105 & DP83822 PHYs - * - * @param dev Pointer to the device data - * @return 1 if the PHY indicates link up, 0 if the link is down - */ -static uint8_t phy_xlnx_gem_ti_dp83822_poll_lsts(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - uint16_t phy_data; - - /* - * Double read of the BMSR is intentional - the relevant bit is latched - * low so that after a link down -> link up transition, the first read - * of the BMSR will still return the latched link down status rather - * than the current status. - */ - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_STATUS_REGISTER); - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_BASIC_MODE_STATUS_REGISTER); - - return ((phy_data & PHY_TI_BASIC_MODE_STATUS_LINK_STATUS_BIT) != 0); -} - -/** - * @brief TI TLK105 & DP83822 PHY link speed polling function - * Link speed polling function for the TI TLK105 & DP83822 PHYs - * - * @param dev Pointer to the device data - * @return Enum containing the current link speed reported by the PHY - */ -static enum eth_xlnx_link_speed phy_xlnx_gem_ti_dp83822_poll_lspd( - const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - enum eth_xlnx_link_speed link_speed; - uint16_t phy_data; - - phy_data = phy_xlnx_gem_mdio_read(dev_conf->base_addr, dev_data->phy_addr, - PHY_TI_PHY_STATUS_REGISTER); - - /* PHYSCR[0] is the link established indication bit */ - if ((phy_data & PHY_TI_PHY_STATUS_LINK_BIT) != 0) { - /* PHYSCR[1] is the speed status bit: 0 = 100 Mbps, 1 = 10 Mbps. */ - if ((phy_data & PHY_TI_PHY_STATUS_SPEED_BIT) != 0) { - link_speed = LINK_10MBIT; - } else { - link_speed = LINK_100MBIT; - } - } else { - link_speed = LINK_DOWN; - } - - return link_speed; -} - -/** - * @brief Marvell Alaska PHY function pointer table - * Function pointer table for the Marvell Alaska PHY series - * specific management functions - */ -static struct phy_xlnx_gem_api phy_xlnx_gem_marvell_alaska_api = { - .phy_reset_func = phy_xlnx_gem_marvell_alaska_reset, - .phy_configure_func = phy_xlnx_gem_marvell_alaska_cfg, - .phy_poll_status_change_func = phy_xlnx_gem_marvell_alaska_poll_sc, - .phy_poll_link_status_func = phy_xlnx_gem_marvell_alaska_poll_lsts, - .phy_poll_link_speed_func = phy_xlnx_gem_marvell_alaska_poll_lspd -}; - -/** - * @brief Texas Instruments TLK105 & DP83822 PHY function pointer table - * Function pointer table for the Texas Instruments TLK105 / DP83822 PHY - * series specific management functions - */ -static struct phy_xlnx_gem_api phy_xlnx_gem_ti_dp83822_api = { - .phy_reset_func = phy_xlnx_gem_ti_dp83822_reset, - .phy_configure_func = phy_xlnx_gem_ti_dp83822_cfg, - .phy_poll_status_change_func = phy_xlnx_gem_ti_dp83822_poll_sc, - .phy_poll_link_status_func = phy_xlnx_gem_ti_dp83822_poll_lsts, - .phy_poll_link_speed_func = phy_xlnx_gem_ti_dp83822_poll_lspd -}; - -/* - * All vendor-specific API structs & code are located above - * -> assemble the top-level list of supported devices the - * upcoming function phy_xlnx_gem_detect will work with. - */ - -/** - * @brief Top-level table of supported PHYs - * Top-level table of PHYs supported by the GEM driver. Contains 1..n - * supported PHY specifications, consisting of the PHY ID plus a mask - * for masking out variable parts of the PHY ID such as hardware revisions, - * as well as a textual description of the PHY model and a pointer to - * the corresponding PHY management function pointer table. - */ -static struct phy_xlnx_gem_supported_dev phy_xlnx_gem_supported_devs[] = { - { - .phy_id = PHY_MRVL_PHY_ID_MODEL_88E1111, - .phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK, - .api = &phy_xlnx_gem_marvell_alaska_api, - .identifier = "Marvell Alaska 88E1111" - }, - { - .phy_id = PHY_MRVL_PHY_ID_MODEL_88E151X, - .phy_id_mask = PHY_MRVL_PHY_ID_MODEL_MASK, - .api = &phy_xlnx_gem_marvell_alaska_api, - .identifier = "Marvell Alaska 88E151x" - }, - { - .phy_id = PHY_TI_PHY_ID_MODEL_DP83822, - .phy_id_mask = PHY_TI_PHY_ID_MODEL_MASK, - .api = &phy_xlnx_gem_ti_dp83822_api, - .identifier = "Texas Instruments DP83822" - }, - { - .phy_id = PHY_TI_PHY_ID_MODEL_TLK105, - .phy_id_mask = PHY_TI_PHY_ID_MODEL_MASK, - .api = &phy_xlnx_gem_ti_dp83822_api, - .identifier = "Texas Instruments TLK105" - } -}; - -/** - * @brief Top-level PHY detection function - * Top-level PHY detection function called by the GEM driver if PHY management - * is enabled for the current GEM device instance. This function is generic - * and does not require any knowledge regarding PHY vendors, models etc. - * - * @param dev Pointer to the device data - * @retval -ENOTSUP if PHY management is disabled for the current GEM - * device instance - * @retval -EIO if no (supported) PHY was detected - * @retval 0 if a supported PHY has been detected - */ -int phy_xlnx_gem_detect(const struct device *dev) -{ - const struct eth_xlnx_gem_dev_cfg *dev_conf = dev->config; - struct eth_xlnx_gem_dev_data *dev_data = dev->data; - - uint8_t phy_curr_addr; - uint8_t phy_first_addr = dev_conf->phy_mdio_addr_fix; - uint8_t phy_last_addr = (dev_conf->phy_mdio_addr_fix != 0) ? - dev_conf->phy_mdio_addr_fix : 31; - uint32_t phy_id; - uint16_t phy_data; - uint32_t list_iter; - - /* - * Clear the PHY address & ID in the device data struct -> may be - * pre-initialized with a non-zero address meaning auto detection - * is disabled. If eventually a supported PHY is found, a non- - * zero address will be written back to the data struct. - */ - dev_data->phy_addr = 0; - dev_data->phy_id = 0; - dev_data->phy_access_api = NULL; - - if (!dev_conf->init_phy) { - return -ENOTSUP; - } - - /* - * PHY detection as described in Zynq-7000 TRM, chapter 16.3.4, - * p. 517 - */ - for (phy_curr_addr = phy_first_addr; - phy_curr_addr <= phy_last_addr; - phy_curr_addr++) { - /* Read the upper & lower PHY ID 16-bit words */ - phy_data = phy_xlnx_gem_mdio_read( - dev_conf->base_addr, phy_curr_addr, - PHY_IDENTIFIER_1_REGISTER); - phy_id = (((uint32_t)phy_data << 16) & 0xFFFF0000); - phy_data = phy_xlnx_gem_mdio_read( - dev_conf->base_addr, phy_curr_addr, - PHY_IDENTIFIER_2_REGISTER); - phy_id |= ((uint32_t)phy_data & 0x0000FFFF); - - if (phy_id != 0x00000000 && phy_id != 0xFFFFFFFF) { - LOG_DBG("%s detected PHY at address %hhu: " - "ID 0x%08X", - dev->name, - phy_curr_addr, phy_id); - - /* - * Iterate the list of all supported PHYs -> if the - * current PHY is supported, store all related data - * in the device's run-time data struct. - */ - for (list_iter = 0; list_iter < ARRAY_SIZE(phy_xlnx_gem_supported_devs); - list_iter++) { - if (phy_xlnx_gem_supported_devs[list_iter].phy_id == - (phy_xlnx_gem_supported_devs[list_iter].phy_id_mask - & phy_id)) { - LOG_DBG("%s identified supported PHY: %s", - dev->name, - phy_xlnx_gem_supported_devs[list_iter].identifier); - - /* - * Store the numeric values of the PHY ID and address - * as well as the corresponding set of function pointers - * in the device's run-time data struct. - */ - dev_data->phy_addr = phy_curr_addr; - dev_data->phy_id = phy_id; - dev_data->phy_access_api = - phy_xlnx_gem_supported_devs[list_iter].api; - - return 0; - } - } - } - } - - LOG_ERR("%s PHY detection failed", dev->name); - return -EIO; -} diff --git a/drivers/ethernet/phy_xlnx_gem.h b/drivers/ethernet/phy_xlnx_gem.h deleted file mode 100644 index 4c93036644549..0000000000000 --- a/drivers/ethernet/phy_xlnx_gem.h +++ /dev/null @@ -1,155 +0,0 @@ -/* - * Xilinx Processor System Gigabit Ethernet controller (GEM) driver - * - * PHY management interface and related data - * - * Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG - * SPDX-License-Identifier: Apache-2.0 - */ - -#ifndef _ZEPHYR_DRIVERS_ETHERNET_PHY_XLNX_GEM_H_ -#define _ZEPHYR_DRIVERS_ETHERNET_PHY_XLNX_GEM_H_ - -#include -#include - -/* Event codes used to indicate a particular state change to the driver */ -#define PHY_XLNX_GEM_EVENT_LINK_SPEED_CHANGED (1 << 0) -#define PHY_XLNX_GEM_EVENT_LINK_STATE_CHANGED (1 << 1) -#define PHY_XLNX_GEM_EVENT_AUTONEG_COMPLETE (1 << 2) - -/* PHY register addresses & constants that are not vendor-specific */ -#define PHY_IDENTIFIER_1_REGISTER 2 -#define PHY_IDENTIFIER_2_REGISTER 3 - -/* PHY registers & constants -> Marvell Alaska specific */ - -/* Marvell PHY ID bits [3..0] = revision -> discard during ID check */ -#define PHY_MRVL_PHY_ID_MODEL_MASK 0xFFFFFFF0 -#define PHY_MRVL_PHY_ID_MODEL_88E1111 0x01410CC0 -#define PHY_MRVL_PHY_ID_MODEL_88E151X 0x01410DD0 - -#define PHY_MRVL_BASE_REGISTERS_PAGE 0 -#define PHY_MRVL_COPPER_CONTROL_REGISTER 0 -#define PHY_MRVL_COPPER_STATUS_REGISTER 1 -#define PHY_MRVL_COPPER_AUTONEG_ADV_REGISTER 4 -#define PHY_MRVL_COPPER_LINK_PARTNER_ABILITY_REGISTER 5 -#define PHY_MRVL_1000BASET_CONTROL_REGISTER 9 -#define PHY_MRVL_COPPER_CONTROL_1_REGISTER 16 -#define PHY_MRVL_COPPER_STATUS_1_REGISTER 17 -#define PHY_MRVL_COPPER_INT_ENABLE_REGISTER 18 -#define PHY_MRVL_COPPER_INT_STATUS_REGISTER 19 -#define PHY_MRVL_COPPER_PAGE_SWITCH_REGISTER 22 -#define PHY_MRVL_GENERAL_CONTROL_1_REGISTER 20 -#define PHY_MRVL_GENERAL_CONTROL_1_PAGE 18 - -#define PHY_MRVL_GENERAL_CONTROL_1_RESET_BIT (1 << 15) - -#define PHY_MRVL_COPPER_CONTROL_RESET_BIT (1 << 15) -#define PHY_MRVL_COPPER_CONTROL_AUTONEG_ENABLE_BIT (1 << 12) - -#define PHY_MRVL_ADV_1000BASET_FDX_BIT (1 << 9) -#define PHY_MRVL_ADV_1000BASET_HDX_BIT (1 << 8) -#define PHY_MRVL_ADV_100BASET_FDX_BIT (1 << 8) -#define PHY_MRVL_ADV_100BASET_HDX_BIT (1 << 7) -#define PHY_MRVL_ADV_10BASET_FDX_BIT (1 << 6) -#define PHY_MRVL_ADV_10BASET_HDX_BIT (1 << 5) -#define PHY_MRVL_ADV_SELECTOR_802_3 0x0001 - -#define PHY_MRVL_MDIX_CONFIG_MASK 0x0003 -#define PHY_MRVL_MDIX_CONFIG_SHIFT 5 -#define PHY_MRVL_MDIX_AUTO_CROSSOVER_ENABLE 0x0003 -#define PHY_MRVL_MODE_CONFIG_MASK 0x0007 -#define PHY_MRVL_MODE_CONFIG_SHIFT 0 - -#define PHY_MRVL_COPPER_SPEED_CHANGED_INT_BIT (1 << 14) -#define PHY_MRVL_COPPER_DUPLEX_CHANGED_INT_BIT (1 << 13) -#define PHY_MRVL_COPPER_AUTONEG_COMPLETED_INT_BIT (1 << 11) -#define PHY_MRVL_COPPER_LINK_STATUS_CHANGED_INT_BIT (1 << 10) -#define PHY_MRVL_COPPER_LINK_STATUS_BIT_SHIFT 5 - -#define PHY_MRVL_LINK_SPEED_SHIFT 14 -#define PHY_MRVL_LINK_SPEED_MASK 0x3 -#define PHY_MRVL_LINK_SPEED_10MBIT 0 -#define PHY_MRVL_LINK_SPEED_100MBIT 1 -#define PHY_MRVL_LINK_SPEED_1GBIT 2 - -/*TI TLK105 & DP83822*/ - -/* TI PHY ID bits [3..0] = revision -> discard during ID check */ -#define PHY_TI_PHY_ID_MODEL_MASK 0xFFFFFFF0 -#define PHY_TI_PHY_ID_MODEL_DP83822 0x2000A240 -#define PHY_TI_PHY_ID_MODEL_TLK105 0x2000A210 - -#define PHY_TI_PHY_SPECIFIC_CONTROL_REGISTER 0x0010 -#define PHY_TI_BASIC_MODE_CONTROL_REGISTER 0x0000 -#define PHY_TI_BASIC_MODE_STATUS_REGISTER 0x0001 -#define PHY_TI_AUTONEG_ADV_REGISTER 0x0004 -#define PHY_TI_CONTROL_REGISTER_1 0x0009 -#define PHY_TI_PHY_STATUS_REGISTER 0x0010 -#define PHY_TI_MII_INTERRUPT_STATUS_REGISTER_1 0x0012 -#define PHY_TI_LED_CONTROL_REGISTER 0x0018 -#define PHY_TI_PHY_CONTROL_REGISTER 0x0019 - -#define PHY_TI_BASIC_MODE_CONTROL_RESET_BIT (1 << 15) -#define PHY_TI_BASIC_MODE_CONTROL_AUTONEG_ENABLE_BIT (1 << 12) - -#define PHY_TI_BASIC_MODE_STATUS_LINK_STATUS_BIT (1 << 2) - -#define PHY_TI_LINK_STATUS_CHANGED_INT_BIT (1 << 13) -#define PHY_TI_SPEED_CHANGED_INT_BIT (1 << 12) -#define PHY_TI_DUPLEX_CHANGED_INT_BIT (1 << 11) -#define PHY_TI_AUTONEG_COMPLETED_INT_BIT (1 << 10) - -#define PHY_TI_ADV_SELECTOR_802_3 0x0001 -#define PHY_TI_ADV_100BASET_FDX_BIT (1 << 8) -#define PHY_TI_ADV_100BASET_HDX_BIT (1 << 7) -#define PHY_TI_ADV_10BASET_FDX_BIT (1 << 6) -#define PHY_TI_ADV_10BASET_HDX_BIT (1 << 5) - -#define PHY_TI_CR1_ROBUST_AUTO_MDIX_BIT (1 << 5) - -#define PHY_TI_PHY_CONTROL_AUTO_MDIX_ENABLE_BIT (1 << 15) -#define PHY_TI_PHY_CONTROL_FORCE_MDIX_BIT (1 << 14) -#define PHY_TI_PHY_CONTROL_LED_CONFIG_LINK_ONLY_BIT (1 << 5) - -#define PHY_TI_LED_CONTROL_BLINK_RATE_SHIFT 9 -#define PHY_TI_LED_CONTROL_BLINK_RATE_20HZ 0 -#define PHY_TI_LED_CONTROL_BLINK_RATE_10HZ 1 -#define PHY_TI_LED_CONTROL_BLINK_RATE_5HZ 2 -#define PHY_TI_LED_CONTROL_BLINK_RATE_2HZ 3 - -#define PHY_TI_PHY_STATUS_LINK_BIT (1 << 0) -#define PHY_TI_PHY_STATUS_SPEED_BIT (1 << 1) - -/** - * @brief Vendor-specific PHY management function pointer table struct - * - * Contains the PHY management function pointers for a specific PHY - * make or model. - */ -struct phy_xlnx_gem_api { - void (*phy_reset_func)(const struct device *dev); - void (*phy_configure_func)(const struct device *dev); - uint16_t (*phy_poll_status_change_func)(const struct device *dev); - uint8_t (*phy_poll_link_status_func)(const struct device *dev); - enum eth_xlnx_link_speed (*phy_poll_link_speed_func)(const struct device *dev); -}; - -/** - * @brief Supported PHY list entry struct - * - * Contains the PHY management function pointers for a specific PHY - * make or model. - */ -struct phy_xlnx_gem_supported_dev { - uint32_t phy_id; - uint32_t phy_id_mask; - struct phy_xlnx_gem_api *api; - const char *identifier; -}; - -/* PHY identification function -> generic, not vendor-specific */ -int phy_xlnx_gem_detect(const struct device *dev); - -#endif /* _ZEPHYR_DRIVERS_ETHERNET_PHY_XLNX_GEM_H_ */ diff --git a/drivers/mdio/CMakeLists.txt b/drivers/mdio/CMakeLists.txt index fc3ce620c202e..b58ba4bf6a0d3 100644 --- a/drivers/mdio/CMakeLists.txt +++ b/drivers/mdio/CMakeLists.txt @@ -20,3 +20,4 @@ zephyr_library_sources_ifdef(CONFIG_MDIO_RENESAS_RA mdio_renesas_ra.c) zephyr_library_sources_ifdef(CONFIG_MDIO_LAN865X mdio_lan865x.c) zephyr_library_sources_ifdef(CONFIG_MDIO_SENSRY_SY1XX mdio_sy1xx.c) zephyr_library_sources_ifdef(CONFIG_MDIO_XILINX_AXI_ENET mdio_xilinx_axienet.c) +zephyr_library_sources_ifdef(CONFIG_MDIO_XLNX_GEM mdio_xlnx_gem.c) diff --git a/drivers/mdio/Kconfig b/drivers/mdio/Kconfig index 658bbe78b7b99..b7c996b681812 100644 --- a/drivers/mdio/Kconfig +++ b/drivers/mdio/Kconfig @@ -41,6 +41,7 @@ source "drivers/mdio/Kconfig.renesas_ra" source "drivers/mdio/Kconfig.lan865x" source "drivers/mdio/Kconfig.sy1xx" source "drivers/mdio/Kconfig.xilinx_axienet" +source "drivers/mdio/Kconfig.xlnx_gem" config MDIO_INIT_PRIORITY int "Init priority" diff --git a/drivers/mdio/Kconfig.xlnx_gem b/drivers/mdio/Kconfig.xlnx_gem new file mode 100644 index 0000000000000..0c9b9d1261f21 --- /dev/null +++ b/drivers/mdio/Kconfig.xlnx_gem @@ -0,0 +1,28 @@ +# Copyright (c) 2025 Immo Birnbaum +# SPDX-License-Identifier: Apache-2.0 + +config MDIO_XLNX_GEM + bool "Xilinx GEM MDIO controller driver" + default y + depends on DT_HAS_XLNX_GEM_MDIO_ENABLED + help + Enable Xilinx GEM MDIO support. + +if MDIO_XLNX_GEM + +config MDIO_XLNX_GEM_MAX_POLL_RETRIES + int "Poll MDIO transaction completion max. retries" + default 10 + help + The number of times the completion of an MDIO + transaction is polled before it is considered + timed out. + +config MDIO_XLNX_GEM_POLL_DELAY + int "Poll MDIO transaction completion delay" + default 50 + help + Delay in microseconds between two MDIO transaction + completion polling operations. + +endif # MDIO_XLNX_GEM diff --git a/drivers/mdio/mdio_xlnx_gem.c b/drivers/mdio/mdio_xlnx_gem.c new file mode 100644 index 0000000000000..df35fa4d39053 --- /dev/null +++ b/drivers/mdio/mdio_xlnx_gem.c @@ -0,0 +1,302 @@ +/* + * Copyright (c) 2021 Weidmueller Interface GmbH & Co. KG + * Copyright (c) 2025 Immo Birnbaum + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +LOG_MODULE_REGISTER(xlnx_gem_mdio, CONFIG_MDIO_LOG_LEVEL); + +#define DT_DRV_COMPAT xlnx_gem_mdio + +/* + * Subset of register offsets and control bits / masks required for MDIO: + * + * Register offsets within the respective GEM's address space: + * NWCTRL = gem.net_ctrl Network Control register + * NWCFG = gem.net_cfg Network Configuration register + * NWSR = gem.net_status Network Status register + * PHYMNTNC = gem.phy_maint PHY maintenance register + * + * gem.net_ctrl: + * [04] Enable MDIO port + * gem.net_cfg: + * [20 .. 18] MDC clock division setting + * gem.net_status: + * [02] PHY management idle bit + * [01] MDIO input status + * gem.phy_maint: + * [31 .. 30] constant values + * [17 .. 16] constant values + * [29] Read operation control bit + * [28] Write operation control bit + * [27 .. 23] PHY address + * [22 .. 18] Register address + * [15 .. 00] 16-bit data word + */ +#define ETH_XLNX_GEM_NWCTRL_OFFSET 0x00000000 +#define ETH_XLNX_GEM_NWCTRL_MDEN_BIT BIT(4) + +#define ETH_XLNX_GEM_NWCFG_OFFSET 0x00000004 +#define ETH_XLNX_GEM_NWCFG_MDC_MASK 0x7 +#define ETH_XLNX_GEM_NWCFG_MDC_SHIFT 18 + +#define ETH_XLNX_GEM_NWSR_OFFSET 0x00000008 +#define ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT BIT(2) + +#define ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET 0x00000034 +#define ETH_XLNX_GEM_PHY_MAINT_CONST_BITS 0x40020000 +#define ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT BIT(29) +#define ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT BIT(28) +#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK 0x0000001F +#define ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT 23 +#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK 0x0000001F +#define ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT 18 +#define ETH_XLNX_GEM_PHY_MAINT_DATA_MASK 0x0000FFFF + +/** + * @brief MDC clock divider configuration enumeration type. + * + * Enumeration type containing the supported clock divider values + * used to generate the MDIO interface clock (MDC) from either the + * cpu_1x clock (Zynq-7000) or the LPD LSBUS clock (ZynqMP). + * This is a configuration item in the controller's net_cfg register. + */ +enum eth_xlnx_mdc_clock_divider { + MDC_DIVIDER_8 = 0, + MDC_DIVIDER_16, + MDC_DIVIDER_32, + MDC_DIVIDER_48, + MDC_DIVIDER_64, + MDC_DIVIDER_96, + MDC_DIVIDER_128, + MDC_DIVIDER_224 +}; + +/** + * @brief Constant device configuration data structure. + * + * This struct contains all device configuration data for a GEM + * MDIO interface instance which is constant. + */ +struct xlnx_gem_mdio_config { + uint32_t gem_base_addr; +}; + +/** + * @brief GEM MDIO interface data read function + * + * @param dev Pointer to the GEM MDIO device + * @param prtad MDIO address of the PHY to be accessed + * @param regad Index of the PHY register to be read + * @param data Read data output pointer + * @return 0 in case of success, -ETIMEDOUT if the read operation + * timed out (idle bit not set as expected) + */ +static int xlnx_gem_mdio_read(const struct device *dev, uint8_t prtad, uint8_t regad, + uint16_t *data) +{ + const struct xlnx_gem_mdio_config *const dev_conf = dev->config; + + uint32_t reg_val; + uint32_t poll_cnt = 0; + + /* + * MDIO read operation as described in Zynq-7000 TRM, + * chapter 16.3.4, p. 517. + */ + + /* + * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the + * current command. + */ + do { + if (poll_cnt++ > 0) { + k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY); + } + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET); + } while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 && + poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES); + if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) { + LOG_ERR("%s: read from PHY address %hhu, register address %hhu timed out", + dev->name, prtad, regad); + return -ETIMEDOUT; + } + + /* Assemble & write the read command to the gem.phy_maint register */ + + /* Set the bits constant for any operation */ + reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS; + /* Indicate a read operation */ + reg_val |= ETH_XLNX_GEM_PHY_MAINT_READ_OP_BIT; + /* PHY address */ + reg_val |= (((uint32_t)prtad & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) + << ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT); + /* Register address */ + reg_val |= (((uint32_t)regad & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) + << ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT); + + sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); + + /* + * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command + * completed. + */ + poll_cnt = 0; + do { + if (poll_cnt++ > 0) { + k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY); + } + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET); + } while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 && + poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES); + if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) { + LOG_ERR("%s: read from PHY address %hhu, register address %hhu timed out", + dev->name, prtad, regad); + return -ETIMEDOUT; + } + + /* + * Read the data returned by the PHY -> lower 16 bits of the PHY main- + * tenance register + */ + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); + + *data = (uint16_t)reg_val; + return 0; +} + +/** + * @brief GEM MDIO interface data write function + * + * @param dev Pointer to the GEM MDIO device + * @param prtad MDIO address of the PHY to be accessed + * @param regad Index of the PHY register to write to + * @param data Data word to be written to the target register + * @return 0 in case of success, -ETIMEDOUT if the read operation + * timed out (idle bit not set as expected) + */ +static int xlnx_gem_mdio_write(const struct device *dev, uint8_t prtad, uint8_t regad, + uint16_t data) +{ + const struct xlnx_gem_mdio_config *const dev_conf = dev->config; + + uint32_t reg_val; + uint32_t poll_cnt = 0; + + /* + * MDIO write operation as described in Zynq-7000 TRM, + * chapter 16.3.4, p. 517. + */ + + /* + * Wait until gem.net_status[phy_mgmt_idle] == 1 before issuing the + * current command. + */ + do { + if (poll_cnt++ > 0) { + k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY); + } + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET); + } while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 && + poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES); + if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) { + LOG_ERR("%s: write to PHY address %hhu, register address %hhu timed out", dev->name, + prtad, regad); + return -ETIMEDOUT; + } + + /* Assemble & write the read command to the gem.phy_maint register */ + + /* Set the bits constant for any operation */ + reg_val = ETH_XLNX_GEM_PHY_MAINT_CONST_BITS; + /* Indicate a read operation */ + reg_val |= ETH_XLNX_GEM_PHY_MAINT_WRITE_OP_BIT; + /* PHY address */ + reg_val |= (((uint32_t)prtad & ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_MASK) + << ETH_XLNX_GEM_PHY_MAINT_PHY_ADDRESS_SHIFT); + /* Register address */ + reg_val |= (((uint32_t)regad & ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_MASK) + << ETH_XLNX_GEM_PHY_MAINT_REGISTER_ID_SHIFT); + /* 16 bits of data for the destination register */ + reg_val |= ((uint32_t)data & ETH_XLNX_GEM_PHY_MAINT_DATA_MASK); + + sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_PHY_MAINTENANCE_OFFSET); + + /* + * Wait until gem.net_status[phy_mgmt_idle] == 1 -> current command + * completed. + */ + poll_cnt = 0; + do { + if (poll_cnt++ > 0) { + k_busy_wait(CONFIG_MDIO_XLNX_GEM_POLL_DELAY); + } + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWSR_OFFSET); + } while ((reg_val & ETH_XLNX_GEM_NWSR_MDIO_IDLE_BIT) == 0 && + poll_cnt < CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES); + if (poll_cnt == CONFIG_MDIO_XLNX_GEM_MAX_POLL_RETRIES) { + LOG_ERR("%s: write to PHY address %hhu, register address %hhu timed out", dev->name, + prtad, regad); + return -ETIMEDOUT; + } + + return 0; +} + +/** + * @brief GEM MDIO interface initialization function + * Configures the MDC clock divider in the associated GEM instance's + * net_config (NWCFG) register and sets the MDIO enable bit in the + * net_control (NWCTRL) register. + * + * @param dev Pointer to the GEM MDIO device + * @return always returns 0 + */ +static int xlnx_gem_mdio_initialize(const struct device *dev) +{ + const struct xlnx_gem_mdio_config *const dev_conf = dev->config; + + uint32_t reg_val; + uint32_t mdc_divider = (uint32_t)MDC_DIVIDER_224; + + /* Set the MDC divider in gem.net_config */ + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); + reg_val &= ~(ETH_XLNX_GEM_NWCFG_MDC_MASK << ETH_XLNX_GEM_NWCFG_MDC_SHIFT); + reg_val |= ((mdc_divider & ETH_XLNX_GEM_NWCFG_MDC_MASK) << ETH_XLNX_GEM_NWCFG_MDC_SHIFT); + sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCFG_OFFSET); + + /* Enable the MDIO interface */ + reg_val = sys_read32(dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); + reg_val |= ETH_XLNX_GEM_NWCTRL_MDEN_BIT; + sys_write32(reg_val, dev_conf->gem_base_addr + ETH_XLNX_GEM_NWCTRL_OFFSET); + + LOG_DBG("%s: initialized", dev->name); + return 0; +} + +static DEVICE_API(mdio, xlnx_gem_mdio_api) = { + .read = xlnx_gem_mdio_read, + .write = xlnx_gem_mdio_write, +}; + +#define XLNX_GEM_MDIO_DEV_CONFIG(port) \ + static const struct xlnx_gem_mdio_config xlnx_gem##port##_mdio_cfg = { \ + .gem_base_addr = DT_REG_ADDR_BY_IDX(DT_INST(port, xlnx_gem), 0), \ + }; + +#define XLNX_GEM_MDIO_DEV_INIT(port) \ + DEVICE_DT_INST_DEFINE(port, &xlnx_gem_mdio_initialize, NULL, NULL, \ + &xlnx_gem##port##_mdio_cfg, POST_KERNEL, CONFIG_MDIO_INIT_PRIORITY, \ + &xlnx_gem_mdio_api); + +#define XLNX_GEM_MDIO_INITIALIZE(port) \ + XLNX_GEM_MDIO_DEV_CONFIG(port); \ + XLNX_GEM_MDIO_DEV_INIT(port); + +DT_INST_FOREACH_STATUS_OKAY(XLNX_GEM_MDIO_INITIALIZE) diff --git a/dts/arm/xilinx/zynq7000.dtsi b/dts/arm/xilinx/zynq7000.dtsi index cbbc8eecaba81..1f62453952fdc 100644 --- a/dts/arm/xilinx/zynq7000.dtsi +++ b/dts/arm/xilinx/zynq7000.dtsi @@ -58,9 +58,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -72,7 +69,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem0_mdio: gem0_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; gem1: ethernet@e000c000 { @@ -85,9 +88,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -99,7 +99,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem1_mdio: gem1_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; uart0: uart@e0000000 { diff --git a/dts/arm/xilinx/zynqmp.dtsi b/dts/arm/xilinx/zynqmp.dtsi index ecadbdfda532c..be9c29bc7a70a 100644 --- a/dts/arm/xilinx/zynqmp.dtsi +++ b/dts/arm/xilinx/zynqmp.dtsi @@ -111,9 +111,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -125,7 +122,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem0_mdio: gem0_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; gem1: ethernet@ff0c0000 { @@ -138,9 +141,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -152,7 +152,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem1_mdio: gem1_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; gem2: ethernet@ff0d0000 { @@ -165,9 +171,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -179,7 +182,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem2_mdio: gem2_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; gem3: ethernet@ff0e0000 { @@ -192,9 +201,6 @@ ; interrupt-names = "irq_0", "irq_1"; - mdio-phy-address = ; - phy-poll-interval = <1000>; - link-speed = ; amba-ahb-dbus-width = ; amba-ahb-burst-length = ; hw-rx-buffer-size = ; @@ -206,7 +212,13 @@ tx-buffer-size = <512>; discard-rx-fcs; unicast-hash; - full-duplex; + }; + + gem3_mdio: gem3_mdio { + compatible = "xlnx,gem-mdio"; + status = "disabled"; + #address-cells = <1>; + #size-cells = <0>; }; psgpio: gpio@ff0a0000 { diff --git a/dts/bindings/ethernet/marvell,88e1xxx-phy.yaml b/dts/bindings/ethernet/marvell,88e1xxx-phy.yaml new file mode 100644 index 0000000000000..0fe9dca6208d4 --- /dev/null +++ b/dts/bindings/ethernet/marvell,88e1xxx-phy.yaml @@ -0,0 +1,15 @@ +# Copyright (c) 2025 Immo Birnbaum +# SPDX-License-Identifier: Apache-2.0 + +description: Marvell Alaska 88E1xxx 1 GBit/s PHY family + +compatible: "marvell,88e1xxx" + +include: phy.yaml + +on-bus: mdio + +properties: + reg: + required: true + description: 5-bit physical/port address (PRTAD). diff --git a/dts/bindings/ethernet/ti,dp83822-phy.yaml b/dts/bindings/ethernet/ti,dp83822-phy.yaml new file mode 100644 index 0000000000000..06dcea6e33202 --- /dev/null +++ b/dts/bindings/ethernet/ti,dp83822-phy.yaml @@ -0,0 +1,16 @@ +# Copyright (c) 2025 Immo Birnbaum +# SPDX-License-Identifier: Apache-2.0 + +description: | + Texas Instruments DP83822 100 MBit/s PHY + +compatible: "ti,dp83822" + +include: ethernet-phy.yaml + +on-bus: mdio + +properties: + reg: + required: true + description: 5-bit physical/port address (PRTAD). diff --git a/dts/bindings/ethernet/xlnx,gem.yaml b/dts/bindings/ethernet/xlnx,gem.yaml index b2f0f2a3e6fc1..9281c5471d292 100644 --- a/dts/bindings/ethernet/xlnx,gem.yaml +++ b/dts/bindings/ethernet/xlnx,gem.yaml @@ -28,66 +28,6 @@ properties: item must be set to the clock frequency of the PLL supplying the respective GEM's TX clock - by default, this is the IO PLL. - mdc-divider: - type: int - required: true - description: | - The MDC clock divider for the respective GEM. This is the divider - applied to the LPD_LSBUS clock in order to derive MDIO interface - clock driving communications with the attached PHY. Refer to the - ZynqMP register documentation (ug1087), network_config (GEM) Register - Description, bits [20:18] to determine the appropriate divider for - the current target's LPD LSBUS clock frequency. - - init-mdio-phy: - type: boolean - description: | - Activates the management of a PHY associated with the controller in- - stance. If this parameter is activated at the board level, the de- - fault values of the associated parameters mdio-phy-address, phy-poll- - interval, link-speed and advertise-lower-link-speeds should be checked - and overwritten at the board level if required. - - mdio-phy-address: - type: int - required: true - description: | - The address on the MDIO bus of the PHY associated with the controller - instance. Set the address to 0 for auto-detection (first responding - PHY will be claimed by the driver, watch out in case of shared MDIO - use), or to a fixed address between 1 and 32. - - phy-poll-interval: - type: int - required: true - description: | - PHY status polling interval in milliseconds for a driver instance - managing an associated PHY. - - link-speed: - type: int - required: true - description: | - Nominal link speed. If no PHY is managed by an instance of this driver, - the respective controller will be configured to match the link speed - specified here. If a PHY is managed by the driver, advertisement of - the link speed specified here will be requested. If the optional pro- - perty advertise-lower-link-speeds is set, advertisement of the link - speed specified here plus any valid link speed below this value will - be requested. - enum: - - 1 - - 2 - - 3 - - advertise-lower-link-speeds: - type: boolean - description: | - Indicates to a driver instance which manages an associated PHY on - the MDIO bus to include link speeds lower than the nominal value - set in the link-speed property in the advertisement when requesting - link speed auto-negotiation with a peer system. - handle-rx-in-isr: type: boolean description: | @@ -318,11 +258,6 @@ properties: description: Optional feature flag - Discard non-VLAN frames. When set, only VLAN tagged frames will be passed to the address matching logic. - full-duplex: - type: boolean - description: | - Optional feature flag - Enables full duplex reception and transmission. - discard-rx-frame-ahb-unavail: type: boolean description: | diff --git a/dts/bindings/mdio/xlnx,gem-mdio.yaml b/dts/bindings/mdio/xlnx,gem-mdio.yaml new file mode 100644 index 0000000000000..52bb7c9f22f9e --- /dev/null +++ b/dts/bindings/mdio/xlnx,gem-mdio.yaml @@ -0,0 +1,9 @@ +# Copyright (c) 2021, Weidmueller Interface GmbH & Co. KG +# Copyright (c) 2025 Immo Birnbaum +# SPDX-License-Identifier: Apache-2.0 + +description: Xilinx GEM MDIO node + +compatible: "xlnx,gem-mdio" + +include: mdio-controller.yaml diff --git a/include/zephyr/dt-bindings/ethernet/xlnx_gem.h b/include/zephyr/dt-bindings/ethernet/xlnx_gem.h index bfb0e0cc06214..11f8e548d17ba 100644 --- a/include/zephyr/dt-bindings/ethernet/xlnx_gem.h +++ b/include/zephyr/dt-bindings/ethernet/xlnx_gem.h @@ -6,39 +6,6 @@ #ifndef ZEPHYR_INCLUDE_DT_BINDINGS_ETHERNET_XLNX_GEM_H_ #define ZEPHYR_INCLUDE_DT_BINDINGS_ETHERNET_XLNX_GEM_H_ -/* PHY auto-detection alias */ -#define XLNX_GEM_PHY_AUTO_DETECT 0 - -/* - * MDC divider values - * - * According to the ZynqMP's gem.network_config register documentation (UG1087), - * divider /32 is the reset value. The network_config[mdc_clock_division] - * documentation in UG1087 is likely wrong (copied directly from the Zynq-7000), - * as it claims that the MDC clock division is applied to the cpu_1x clock - * which the UltraScale doesn't have. Contradicting information is provided in - * the UltraScale TRM (UG1085), which mentions in chapter 34, section "Configure - * the PHY", p. 1074, that the MDC clock division is applied to the IOU_SWITCH_CLK. - * Xilinx's emacps driver doesn't (or no longer does) limit the range of dividers - * on the UltraScale compared to the Zynq-7000. - * -> Contrary to earlier revisions of this driver, all dividers are available - * to both the UltraScale and the Zynq-7000. - */ - -#define XLNX_GEM_MDC_DIVIDER_8 0 /* cpu_1x or IOU_SWITCH_CLK < 20 MHz */ -#define XLNX_GEM_MDC_DIVIDER_16 1 /* cpu_1x or IOU_SWITCH_CLK 20 - 40 MHz */ -#define XLNX_GEM_MDC_DIVIDER_32 2 /* cpu_1x or IOU_SWITCH_CLK 40 - 80 MHz */ -#define XLNX_GEM_MDC_DIVIDER_48 3 /* cpu_1x or IOU_SWITCH_CLK 80 - 120 MHz */ -#define XLNX_GEM_MDC_DIVIDER_64 4 /* cpu_1x or IOU_SWITCH_CLK 120 - 160 MHz */ -#define XLNX_GEM_MDC_DIVIDER_96 5 /* cpu_1x or IOU_SWITCH_CLK 160 - 240 MHz */ -#define XLNX_GEM_MDC_DIVIDER_128 6 /* cpu_1x or IOU_SWITCH_CLK 240 - 320 MHz */ -#define XLNX_GEM_MDC_DIVIDER_224 7 /* cpu_1x or IOU_SWITCH_CLK 320 - 540 MHz */ - -/* Link speed values */ -#define XLNX_GEM_LINK_SPEED_10MBIT 1 -#define XLNX_GEM_LINK_SPEED_100MBIT 2 -#define XLNX_GEM_LINK_SPEED_1GBIT 3 - /* AMBA AHB data bus width */ #define XLNX_GEM_AMBA_AHB_DBUS_WIDTH_32BIT 0 #define XLNX_GEM_AMBA_AHB_DBUS_WIDTH_64BIT 1 diff --git a/soc/xlnx/zynq7000/xc7zxxx/soc.c b/soc/xlnx/zynq7000/xc7zxxx/soc.c index 5bc0c422b1546..b63d3274df49c 100644 --- a/soc/xlnx/zynq7000/xc7zxxx/soc.c +++ b/soc/xlnx/zynq7000/xc7zxxx/soc.c @@ -39,6 +39,12 @@ static const struct arm_mmu_region mmu_regions[] = { /* ARM Arch timer, GIC are covered by the MPCore mapping */ /* GEMs */ +#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem0)) || DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem1)) + MMU_REGION_FLAT_ENTRY("slcr", + 0xF8000000, + 0x1000, + MT_STRONGLY_ORDERED | MPERM_R | MPERM_W), +#endif #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem0)) MMU_REGION_FLAT_ENTRY("gem0", DT_REG_ADDR(DT_NODELABEL(gem0)), diff --git a/soc/xlnx/zynq7000/xc7zxxxs/soc.c b/soc/xlnx/zynq7000/xc7zxxxs/soc.c index b19e9e43ce16c..1fb7324a54281 100644 --- a/soc/xlnx/zynq7000/xc7zxxxs/soc.c +++ b/soc/xlnx/zynq7000/xc7zxxxs/soc.c @@ -39,6 +39,12 @@ static const struct arm_mmu_region mmu_regions[] = { /* ARM Arch timer, GIC are covered by the MPCore mapping */ /* GEMs */ +#if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem0)) || DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem1)) + MMU_REGION_FLAT_ENTRY("slcr", + 0xF8000000, + 0x1000, + MT_STRONGLY_ORDERED | MPERM_R | MPERM_W), +#endif #if DT_NODE_HAS_STATUS_OKAY(DT_NODELABEL(gem0)) MMU_REGION_FLAT_ENTRY("gem0", DT_REG_ADDR(DT_NODELABEL(gem0)),