zephyr/drivers/ethernet/phy_xlnx_gem.c
Immo Birnbaum dabe728eef drivers: ethernet: add support for Xilinx GEM controller
Add support for the Xilinx GEM Ethernet controller, which is integrated
in both the Xilinx Zynq and ZynqMP (UltraScale) SoC families. The driver
supports the management of a PHY attached to the respective GEM's MDIO
interface.

This driver was developed with ultimately the Zynq-7000 series in mind,
but at the time being, it is limited to use in conjunction with the
ZynqMP RPU (Cortex-R5) cores. The differences are minor when it comes
to the adjustment of the TX clock frequency derived from the current
link speed reported by the PHY, but for use in conjunction with the
Zynq-7000, some larger adjustments will have to be made when it comes
to the placement of the DMA memory area, as this involves the confi-
guration of the MMU in Cortex-A CPUs.

The driver was developed under the qemu_cortex_r5 target. The Marvell
88E1111 PHY simulated by QEMU is supported by the driver.

Limitations currently exist when it comes to timestamping or VLAN
support and other minor things. Those haven't been implemented yet,
although they are supported by the hardware. In order to be fully
supported by the ZynqMP APU, 64-bit DMA address descriptor format
support will be added.

Signed-off-by: Immo Birnbaum <Immo.Birnbaum@weidmueller.com>
2021-06-21 20:11:00 -04:00

979 lines
33 KiB
C

/*
* 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 <zephyr.h>
#include <device.h>
#include "eth_xlnx_gem_priv.h"
#define LOG_MODULE_NAME phy_xlnx_gem
#define LOG_LEVEL CONFIG_ETHERNET_LOG_LEVEL
#include <logging/log.h>
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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: onfigure 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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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_CFG(dev);
struct eth_xlnx_gem_dev_data *dev_data = DEV_DATA(dev);
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;
}
/* EOF */