/** @file Copyright (c) 2020 Jared McNeill. All rights reserved. Copyright (c) 2020 Andrey Warkentin SPDX-License-Identifier: BSD-2-Clause-Patent **/ #include #include #include #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; }