/** @file
|
|
Copyright (c) 2020 Jared McNeill. All rights reserved.
|
Copyright (c) 2020 Andrey Warkentin <andrey.warkentin@gmail.com>
|
|
SPDX-License-Identifier: BSD-2-Clause-Patent
|
|
**/
|
|
#include <Uefi.h>
|
#include <Library/DebugLib.h>
|
#include <Library/UefiBootServicesTableLib.h>
|
|
#include "GenericPhy.h"
|
|
#define PHY_RESET_TIMEOUT 500
|
|
/**
|
Perform a PHY register read.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
@param PhyAddr[in] PHY address.
|
@param Reg[in] PHY register.
|
@param Data[out] Pointer to register data read.
|
|
@retval EFI_SUCCESS Data read successfully.
|
@retval EFI_DEVICE_ERROR Failed to read data.
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyRead (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy,
|
IN UINT8 PhyAddr,
|
IN UINT8 Reg,
|
OUT UINT16 *Data
|
)
|
{
|
return Phy->Read (Phy->PrivateData, PhyAddr, Reg, Data);
|
}
|
|
/**
|
Perform a PHY register write.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
@param PhyAddr[in] PHY address.
|
@param Reg[in] PHY register.
|
@param Data[in] Pointer to register data to write.
|
|
@retval EFI_SUCCESS Data written successfully.
|
@retval EFI_DEVICE_ERROR Failed to write data.
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyWrite (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy,
|
IN UINT8 PhyAddr,
|
IN UINT8 Reg,
|
IN UINT16 Data
|
)
|
{
|
return Phy->Write (Phy->PrivateData, PhyAddr, Reg, Data);
|
}
|
|
/**
|
Process a PHY link speed change (e.g. with MAC layer).
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
@param Speed[in] Speed setting.
|
@param Duplex[in] Duplex setting.
|
|
**/
|
STATIC
|
VOID
|
GenericPhyConfigure (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy,
|
IN GENERIC_PHY_SPEED Speed,
|
IN GENERIC_PHY_DUPLEX Duplex
|
)
|
{
|
Phy->Configure (Phy->PrivateData, Speed, Duplex);
|
}
|
|
/**
|
Detect address for the first PHY seen, probing all possible addresses.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Found a PHY and programmed Phy->PhyAddr
|
@retval EFI_DEVICE_ERROR Error reading/writing a PHY register.
|
@retval EFI_NOT_FOUND No PHY detected.
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyDetect (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
UINT8 PhyAddr;
|
UINT16 Id1, Id2;
|
|
for (PhyAddr = 0; PhyAddr < 32; PhyAddr++) {
|
Status = GenericPhyRead (Phy, PhyAddr, GENERIC_PHY_PHYIDR1, &Id1);
|
if (EFI_ERROR (Status)) {
|
continue;
|
}
|
Status = GenericPhyRead (Phy, PhyAddr, GENERIC_PHY_PHYIDR2, &Id2);
|
if (EFI_ERROR (Status)) {
|
continue;
|
}
|
if (Id1 != 0xFFFF && Id2 != 0xFFFF) {
|
Phy->PhyAddr = PhyAddr;
|
DEBUG ((DEBUG_INFO,
|
"%a: PHY detected at address 0x%02X (PHYIDR1=0x%04X, PHYIDR2=0x%04X)\n",
|
__FUNCTION__, PhyAddr, Id1, Id2));
|
return EFI_SUCCESS;
|
}
|
}
|
|
return EFI_NOT_FOUND;
|
}
|
|
/**
|
Start link auto-negotiation on a PHY.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Auto-netogiation started.
|
@retval EFI_DEVICE_ERROR PHY register read/write error.
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyAutoNegotiate (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
UINT16 Anar, Gbcr, Bmcr;
|
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_ANAR, &Anar);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Anar |= GENERIC_PHY_ANAR_100BASETX_FDX |
|
GENERIC_PHY_ANAR_100BASETX |
|
GENERIC_PHY_ANAR_10BASET_FDX |
|
GENERIC_PHY_ANAR_10BASET;
|
Status = GenericPhyWrite (Phy, Phy->PhyAddr, GENERIC_PHY_ANAR, Anar);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_GBCR, &Gbcr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Gbcr |= GENERIC_PHY_GBCR_1000BASET_FDX |
|
GENERIC_PHY_GBCR_1000BASET;
|
Status = GenericPhyWrite (Phy, Phy->PhyAddr, GENERIC_PHY_GBCR, Gbcr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_BMCR, &Bmcr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Bmcr |= GENERIC_PHY_BMCR_ANE |
|
GENERIC_PHY_BMCR_RESTART_AN;
|
return GenericPhyWrite (Phy, Phy->PhyAddr, GENERIC_PHY_BMCR, Bmcr);
|
}
|
|
/**
|
Initialize the first PHY detected, performing a reset and enabling
|
auto-negotiation.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Auto-negotiation started.
|
@retval EFI_DEVICE_ERROR PHY register read/write error.
|
@retval EFI_TIMEOUT PHY reset time-out.
|
@retval EFI_NOT_FOUND No PHY detected.
|
|
**/
|
EFI_STATUS
|
EFIAPI
|
GenericPhyInit (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
|
ASSERT (Phy->Read != NULL);
|
ASSERT (Phy->Write != NULL);
|
ASSERT (Phy->Configure != NULL);
|
|
Status = GenericPhyDetect (Phy);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
Status = GenericPhyReset (Phy);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
return GenericPhyAutoNegotiate (Phy);
|
}
|
|
/**
|
Perform a PHY reset.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Auto-negotiation started.
|
@retval EFI_DEVICE_ERROR PHY register read/write error.
|
@retval EFI_TIMEOUT PHY reset time-out.
|
|
**/
|
EFI_STATUS
|
EFIAPI
|
GenericPhyReset (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
UINTN Retry;
|
UINT16 Data;
|
|
// Start reset sequence
|
Status = GenericPhyWrite (Phy, Phy->PhyAddr, GENERIC_PHY_BMCR,
|
GENERIC_PHY_BMCR_RESET);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
// Wait up to 500ms for it to complete
|
for (Retry = PHY_RESET_TIMEOUT; Retry > 0; Retry--) {
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_BMCR, &Data);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
if ((Data & GENERIC_PHY_BMCR_RESET) == 0) {
|
break;
|
}
|
gBS->Stall (1000);
|
}
|
if (Retry == 0) {
|
return EFI_TIMEOUT;
|
}
|
|
if (Phy->ResetAction != NULL) {
|
Phy->ResetAction (Phy->PrivateData);
|
}
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Probe link status.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Link is up and auto-negotiation is complete.
|
@retval EFI_DEVICE_ERROR PHY register read/write error,
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyGetLinkStatus (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
UINT16 Bmsr;
|
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_BMSR, &Bmsr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
if ((Bmsr & GENERIC_PHY_BMSR_LINK_STATUS) == 0) {
|
return EFI_TIMEOUT;
|
}
|
|
if ((Bmsr & GENERIC_PHY_BMSR_ANEG_COMPLETE) == 0) {
|
return EFI_TIMEOUT;
|
}
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Return PHY link configuration.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
@param Speed[out] Pointer to store link speed.
|
@param Duplex[out] Pointer to store link duplex setting.
|
|
@retval EFI_SUCCESS Link configuration settings read.
|
@retval EFI_DEVICE_ERROR PHY register read/write error,
|
|
**/
|
STATIC
|
EFI_STATUS
|
GenericPhyGetConfig (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy,
|
OUT GENERIC_PHY_SPEED *Speed,
|
OUT GENERIC_PHY_DUPLEX *Duplex
|
)
|
{
|
EFI_STATUS Status;
|
UINT16 Gbcr, Gbsr, Anlpar, Anar;
|
UINT16 Gb, An;
|
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_GBCR, &Gbcr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_GBSR, &Gbsr);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_ANLPAR, &Anlpar);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
Status = GenericPhyRead (Phy, Phy->PhyAddr, GENERIC_PHY_ANAR, &Anar);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
Gb = (Gbsr >> 2) & Gbcr;
|
An = Anlpar & Anar;
|
|
if ((Gb & (GENERIC_PHY_GBCR_1000BASET_FDX |
|
GENERIC_PHY_GBCR_1000BASET)) != 0) {
|
*Speed = PHY_SPEED_1000;
|
*Duplex = (Gb & GENERIC_PHY_GBCR_1000BASET_FDX) ? PHY_DUPLEX_FULL
|
: PHY_DUPLEX_HALF;
|
} else if ((An & (GENERIC_PHY_ANAR_100BASETX_FDX |
|
GENERIC_PHY_ANAR_100BASETX)) != 0) {
|
*Speed = PHY_SPEED_100;
|
*Duplex = (An & GENERIC_PHY_ANAR_100BASETX_FDX) ? PHY_DUPLEX_FULL
|
: PHY_DUPLEX_HALF;
|
} else {
|
*Speed = PHY_SPEED_10;
|
*Duplex = (An & GENERIC_PHY_ANAR_10BASET_FDX) ? PHY_DUPLEX_FULL
|
: PHY_DUPLEX_HALF;
|
}
|
|
DEBUG ((DEBUG_INFO, "%a: Link speed %d Mbps, %a-duplex\n",
|
__FUNCTION__, *Speed, *Duplex == PHY_DUPLEX_FULL ? "full" : "half"));
|
|
return EFI_SUCCESS;
|
}
|
|
/**
|
Update link status, propagating PHY link state into the MAC layer.
|
|
@param Phy[in] Pointer to GENERIC_PHY_PRIVATE_DATA.
|
|
@retval EFI_SUCCESS Link is up.
|
@retval EFI_DEVICE_ERROR PHY register read/write error.
|
@retval EFI_NOT_READY Link is down.
|
|
**/
|
EFI_STATUS
|
EFIAPI
|
GenericPhyUpdateConfig (
|
IN GENERIC_PHY_PRIVATE_DATA *Phy
|
)
|
{
|
EFI_STATUS Status;
|
GENERIC_PHY_SPEED Speed;
|
GENERIC_PHY_DUPLEX Duplex;
|
BOOLEAN LinkUp;
|
|
Status = GenericPhyGetLinkStatus (Phy);
|
LinkUp = EFI_ERROR (Status) ? FALSE : TRUE;
|
|
if (Phy->LinkUp != LinkUp) {
|
if (LinkUp) {
|
DEBUG ((DEBUG_VERBOSE, "%a: Link is up\n", __FUNCTION__));
|
|
Status = GenericPhyGetConfig (Phy, &Speed, &Duplex);
|
if (EFI_ERROR (Status)) {
|
return Status;
|
}
|
|
GenericPhyConfigure (Phy, Speed, Duplex);
|
} else {
|
DEBUG ((DEBUG_VERBOSE, "%a: Link is down\n", __FUNCTION__));
|
}
|
}
|
|
Phy->LinkUp = LinkUp;
|
|
return LinkUp ? EFI_SUCCESS : EFI_NOT_READY;
|
}
|