From 77bbb799ffc3baf125003e4d95c71a5fba90f39c Mon Sep 17 00:00:00 2001 From: Parker Newman Date: Tue, 25 Jul 2023 14:52:22 -0400 Subject: [PATCH 1/3] feat: Add support for Micrel (now Microchip) KSZ9031 1G RGMII Ethernet PHY This patch adds support for the Micrel (now Microchip) KSZ9031 1G RGMII Ethernet PHY used on many Connect Tech carrier boards to the NNIDIA Eqos UEFI Driver. Signed-off-by: Parker Newman --- .../Drivers/EqosDeviceDxe/EqosDeviceDxe.inf | 1 + .../NVIDIA/Drivers/EqosDeviceDxe/PhyDxeUtil.c | 8 + .../NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c | 329 ++++++++++++++++++ .../NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.h | 68 ++++ 4 files changed, 406 insertions(+) create mode 100644 Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c create mode 100644 Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.h diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/EqosDeviceDxe.inf b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/EqosDeviceDxe.inf index 0947ed6dc9..364652d63f 100644 --- a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/EqosDeviceDxe.inf +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/EqosDeviceDxe.inf @@ -34,6 +34,7 @@ PhyDxeUtil.c PhyMarvell.c PhyRealtek.c + PhyMicrel.c PhyMgbe.c DtAcpiMacUpdate.c diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyDxeUtil.c b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyDxeUtil.c index e662e1c746..744e8cb1f3 100644 --- a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyDxeUtil.c +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyDxeUtil.c @@ -23,6 +23,7 @@ #include "PhyMarvell.h" #include "PhyRealtek.h" #include "PhyMgbe.h" +#include "PhyMicrel.h" STATIC EFI_STATUS @@ -234,6 +235,13 @@ PhyConfig ( PhyDriver->DetectLink = PhyRealtekDetectLink; break; + case PHY_MICREL_OUI: + PhyDriver->Config = PhyMicrelConfig; + PhyDriver->StartAutoNeg = PhyMicrelStartAutoNeg; + PhyDriver->CheckAutoNeg = PhyMicrelCheckAutoNeg; + PhyDriver->DetectLink = PhyMicrelDetectLink; + break; + case PHY_MGBE_OUI: PhyDriver->Config = PhyMGBEConfig; PhyDriver->StartAutoNeg = PhyMGBEStartAutoNeg; diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c new file mode 100644 index 0000000000..5f5cc2e6f2 --- /dev/null +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c @@ -0,0 +1,329 @@ +/** @file + + Copyright (c) 2011 - 2019, Intel Corporaton. All rights reserved. + Copyright (c) 2020 - 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + Copyright (c) 2012 - 2014, ARM Limited. All rights reserved. + Copyright (c) 2004 - 2010, Intel Corporation. All rights reserved. + Copyright (c) 2023, Connect Tech Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#include "PhyDxeUtil.h" +#include "PhyMicrel.h" +#include "EmacDxeUtil.h" + +#include +#include +#include +#include +#include +#include +#include +#include + + +/***************************************************************/ +#define REG_PHY_CTRL 0x1F +#define PHY_CTRL_SPEED_1000 BIT6 +#define PHY_CTRL_SPEED_100 BIT5 +#define PHY_CTRL_SPEED_10 BIT4 +#define PHY_CTRL_SPEED_MASK (BIT6|BIT5|BIT4) +#define PHY_CTRL_DUPLEX_MODE BIT3 + +#define PHY_STATUS_LINK BIT2 + +#define PAGE_RGMII_TIMING 2 + +#define REG_PHY_CTRL_SKEW 0x4 +#define REG_PHY_RX_DATA_SKEW 0x5 +#define REG_PHY_TX_DATA_SKEW 0x6 +#define REG_PHY_CLK_SKEW 0x8 + +#define REG_FLP_BURST_TX_LO 0x3 +#define REG_FLP_BURST_TX_HI 0x4 + + +typedef struct { + UINT16 CtrlSkew; + UINT16 DataTxSkew; + UINT16 DataRxSkew; + UINT16 ClkSkew; + UINT16 FLPBurstTxHi; + UINT16 FLPBurstTxLo; +}MICREL_TIMING_VALUES; + + +static const MICREL_TIMING_VALUES TimingsKSZ9031Orin = +{ + .CtrlSkew = 0x0077, + .DataTxSkew = 0x7777, + .DataRxSkew = 0x7777, + .ClkSkew = 0x0379, + .FLPBurstTxHi = 0x0006, + .FLPBurstTxLo = 0x1A80, +}; + + +static const MICREL_TIMING_VALUES TimingsKSZ9031Xavier = +{ + .CtrlSkew = 0x0007, + .DataTxSkew = 0x0000, + .DataRxSkew = 0x7777, + .ClkSkew = 0x03F9, + .FLPBurstTxHi = 0x0006, + .FLPBurstTxLo = 0x1A80, +}; + + +// Start auto-negotiation +EFI_STATUS +EFIAPI +PhyMicrelStartAutoNeg ( + IN PHY_DRIVER *PhyDriver + ) +{ + UINT32 Data32; + + PhyDriver->AutoNegState = PHY_AUTONEG_RUNNING; + + /* Advertise 1000 MBPS full duplex mode */ + PhyRead (PhyDriver, PAGE_PHY, REG_PHY_GB_CONTROL, &Data32); + Data32 |= REG_PHY_GB_CONTROL_ADVERTISE_1000_BASE_T_FULL; + PhyWrite (PhyDriver, PAGE_PHY, REG_PHY_GB_CONTROL, Data32); + + /* Advertise 100, 10 MBPS with full and half duplex mode */ + PhyRead (PhyDriver, PAGE_PHY, REG_PHY_AUTONEG_ADVERTISE, &Data32); + + Data32 |= REG_PHY_AUTONEG_ADVERTISE_100_BASE_T4 | + REG_PHY_AUTONEG_ADVERTISE_100_BASE_TX_FULL | + REG_PHY_AUTONEG_ADVERTISE_100_BASE_TX_HALF | + REG_PHY_AUTONEG_ADVERTISE_10_BASE_T_FULL | + REG_PHY_AUTONEG_ADVERTISE_10_BASE_T_HALF; + + PhyWrite (PhyDriver, PAGE_PHY, REG_PHY_AUTONEG_ADVERTISE, Data32); + + PhyRead (PhyDriver, PAGE_PHY, REG_PHY_CONTROL, &Data32); + Data32 |= REG_PHY_CONTROL_AUTO_NEGOTIATION_ENABLE | REG_PHY_CONTROL_RESTART_AUTO_NEGOTIATION; + + return PhyWrite (PhyDriver, PAGE_PHY, REG_PHY_CONTROL, Data32); +} + +// Check auto-negotiation completion +EFI_STATUS +EFIAPI +PhyMicrelCheckAutoNeg ( + IN PHY_DRIVER *PhyDriver + ) +{ + + UINT64 TimeoutNS; + UINT32 Data32; + EFI_STATUS Status; + + + if (PhyDriver->AutoNegState == PHY_AUTONEG_IDLE) { + return EFI_SUCCESS; + } + + // Only check once if we are in timeout state + if (PhyDriver->AutoNegState == PHY_AUTONEG_TIMEOUT) { + TimeoutNS = GetTimeInNanoSecond (GetPerformanceCounter ()); + } else { + TimeoutNS = GetTimeInNanoSecond (GetPerformanceCounter ()) + (PHY_TIMEOUT * 1000); + } + + do { + // Read PHY_STATUS register + Status = PhyRead (PhyDriver, PAGE_PHY, REG_PHY_STATUS, &Data32); + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Micrel Failed to read PHY_STATUS register\r\n")); + goto Exit; + } + + // Check Auto-Negotiation Complete bit + if ((Data32 & REG_PHY_STATUS_AUTO_NEGOTIATION_COMPLETED) != 0) { + break; + } + + } while (TimeoutNS > GetTimeInNanoSecond (GetPerformanceCounter ())); + + if ((Data32 & REG_PHY_STATUS_AUTO_NEGOTIATION_COMPLETED) == 0) { + Status = EFI_TIMEOUT; + } + +Exit: + if (!EFI_ERROR (Status)) { + PhyDriver->AutoNegState = PHY_AUTONEG_IDLE; + } else if (Status == EFI_TIMEOUT) { + PhyDriver->AutoNegState = PHY_AUTONEG_TIMEOUT; + } + + return Status; +} + +EFI_STATUS +EFIAPI +PhyMicrelGetRGMIITimings ( + IN PHY_DRIVER *PhyDriver, + OUT MICREL_TIMING_VALUES *Timings + ) +{ + UINTN ChipID; + + if (PhyDriver == NULL || Timings == NULL) { + return EFI_INVALID_PARAMETER; + } + + + // TODO: Add device tree parsing support + + ChipID = TegraGetChipID (); + + if (ChipID == T234_CHIP_ID) { + CopyMem(Timings, &TimingsKSZ9031Orin, sizeof(MICREL_TIMING_VALUES)); + } else if (ChipID == T194_CHIP_ID){ + CopyMem(Timings, &TimingsKSZ9031Xavier, sizeof(MICREL_TIMING_VALUES)); + } + else{ + DEBUG ((DEBUG_ERROR, "Micrel: %a: Unsupported Chip ID %X\n", __FUNCTION__, ChipID)); + return EFI_UNSUPPORTED; + } + + return EFI_SUCCESS; +} + +EFI_STATUS +EFIAPI +PhyMicrelSetTimings ( + IN PHY_DRIVER *PhyDriver + ) +{ + + EFI_STATUS Status; + MICREL_TIMING_VALUES Timings; + + Status = PhyMicrelGetRGMIITimings(PhyDriver, &Timings); + if (EFI_ERROR (Status)){ + return Status; + } + + + Status = PhyWrite (PhyDriver, PAGE_RGMII_TIMING, REG_PHY_CTRL_SKEW, Timings.CtrlSkew); + if (EFI_ERROR (Status)){ + return Status; + } + + Status = PhyWrite (PhyDriver, PAGE_RGMII_TIMING, REG_PHY_RX_DATA_SKEW, Timings.DataRxSkew); + if (EFI_ERROR (Status)){ + return Status; + } + + Status = PhyWrite (PhyDriver, PAGE_RGMII_TIMING, REG_PHY_TX_DATA_SKEW, Timings.DataTxSkew); + if (EFI_ERROR (Status)){ + return Status; + } + + Status = PhyWrite (PhyDriver, PAGE_RGMII_TIMING, REG_PHY_CLK_SKEW, Timings.ClkSkew); + if (EFI_ERROR (Status)){ + return Status; + } + + Status = PhyWrite (PhyDriver, PAGE_PHY, REG_FLP_BURST_TX_HI, Timings.FLPBurstTxHi); + if (EFI_ERROR (Status)){ + return Status; + } + + Status = PhyWrite (PhyDriver, PAGE_PHY, REG_FLP_BURST_TX_LO, Timings.FLPBurstTxLo); + if (EFI_ERROR (Status)){ + return Status; + } + + + return Status; +} + +/* + * @brief Configure Micrel PHY + * + * @param PhyDriver PHY object + * + * @return EFI_SUCCESS if success, specific error if fails + */ +EFI_STATUS +EFIAPI +PhyMicrelConfig ( + IN PHY_DRIVER *PhyDriver + ) +{ + //UINT32 Data32; + EFI_STATUS Status; + + Status = PhyMicrelSetTimings(PhyDriver); + + if (EFI_ERROR (Status)) { + DEBUG ((DEBUG_ERROR, "Micrel: %a: Failed to Set RGMII Timings\n", __FUNCTION__)); + return Status; + } + + return EFI_SUCCESS; +} + +/* + * @brief Detect link between Micrel PHY and MAC + * + * @param phy PHY object + */ +VOID +EFIAPI +PhyMicrelDetectLink ( + IN PHY_DRIVER *PhyDriver + ) +{ + UINT32 Data32; + EFI_STATUS Status; + + Status = PhyRead(PhyDriver, PAGE_PHY, REG_PHY_STATUS, &Data32); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "Micrel: Failed to read PHY_STATUS register\r\n")); + return; + } + + if ((Data32 & PHY_STATUS_LINK) == 0) { + PhyDriver->PhyCurrentLink = LINK_DOWN; + } else { + PhyDriver->PhyCurrentLink = LINK_UP; + } + + Status = PhyRead(PhyDriver, PAGE_PHY, REG_PHY_CTRL, &Data32); + + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "Micrel: Failed to read PHY_CTRL register\r\n")); + return; + } + + if (PhyDriver->PhyOldLink != PhyDriver->PhyCurrentLink) { + + if (PhyDriver->PhyCurrentLink == LINK_UP) { + if ((Data32 & PHY_CTRL_DUPLEX_MODE) == 0) { + PhyDriver->Duplex = DUPLEX_HALF; + } else { + PhyDriver->Duplex = DUPLEX_FULL; + } + if ((Data32 & PHY_CTRL_SPEED_MASK) == PHY_CTRL_SPEED_1000) { + PhyDriver->Speed = SPEED_1000; + } else if ((Data32 & PHY_CTRL_SPEED_MASK) == PHY_CTRL_SPEED_100) { + PhyDriver->Speed = SPEED_100; + } else if ((Data32 & PHY_CTRL_SPEED_MASK) == PHY_CTRL_SPEED_10) { + PhyDriver->Speed = SPEED_10; + } else { + PhyDriver->Speed = SPEED_10; + } + DEBUG ((DEBUG_ERROR, "Micrel: Link is up, Speed %dMbps %a Duplex\r\n", + PhyDriver->Speed, (PhyDriver->Duplex == DUPLEX_FULL ? "FULL" : "HALF"))); + } else { + DEBUG ((DEBUG_ERROR, "Micrel: Link is Down\r\n")); + } + } +} diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.h b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.h new file mode 100644 index 0000000000..38540acbe1 --- /dev/null +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.h @@ -0,0 +1,68 @@ +/** @file + + Copyright (c) 2011 - 2019, Intel Corporaton. All rights reserved. + Copyright (c) 2008 - 2009, Apple Inc. All rights reserved. + Copyright (c) 2011 - 2014, ARM Limited. All rights reserved. + Copyright (c) 2020, NVIDIA CORPORATION & AFFILIATES. All rights reserved. + Copyright (c) 2023, Connect Tech Inc. All rights reserved. + + SPDX-License-Identifier: BSD-2-Clause-Patent + +**/ + +#ifndef _PHY_MICREL_H__ +#define _PHY_MICREL_H__ + +#define PHY_MICREL_OUI 0x000885 + +/* + * @brief Configure Micrel PHY + * + * @param PhyDriver PHY object + * + * @return EFI_SUCCESS if success, specific error if fails + */ +EFI_STATUS +EFIAPI +PhyMicrelConfig ( + IN PHY_DRIVER *PhyDriver + ); + +/* + * @brief Start auto-negotiation from Micrel PHY + * + * @param PhyDriver PHY object + * + * @return EFI_SUCCESS if success, specific error if fails + */ +EFI_STATUS +EFIAPI +PhyMicrelStartAutoNeg ( + IN PHY_DRIVER *PhyDriver + ); + +/* + * @brief Check auto-negotiation completion status from Micrel PHY + * + * @param PhyDriver PHY object + * + * @return EFI_SUCCESS if success, specific error if fails + */ +EFI_STATUS +EFIAPI +PhyMicrelCheckAutoNeg ( + IN PHY_DRIVER *PhyDriver + ); + +/* + * @brief Detect link between Micrel PHY and MAC + * + * @param phy PHY object + */ +VOID +EFIAPI +PhyMicrelDetectLink ( + IN PHY_DRIVER *PhyDriver + ); + +#endif /* _PHY_MICREL_H__ */ From f9c748e19c50dab7f7fc5171e17cdb8caf95a6dd Mon Sep 17 00:00:00 2001 From: Parker Newman Date: Tue, 1 Aug 2023 16:18:27 -0400 Subject: [PATCH 2/3] fix: Fix autonegotion advertisement for KSZ9031 This patch sovles an autonegotion advertisement issue that can occur with the Micrel KSZ9031 that prevents the link coming up in UEFI Signed-off-by: Parker Newman --- Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c index 5f5cc2e6f2..8d8ea03d89 100644 --- a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c @@ -93,15 +93,14 @@ PhyMicrelStartAutoNeg ( Data32 |= REG_PHY_GB_CONTROL_ADVERTISE_1000_BASE_T_FULL; PhyWrite (PhyDriver, PAGE_PHY, REG_PHY_GB_CONTROL, Data32); - /* Advertise 100, 10 MBPS with full and half duplex mode */ - PhyRead (PhyDriver, PAGE_PHY, REG_PHY_AUTONEG_ADVERTISE, &Data32); - - Data32 |= REG_PHY_AUTONEG_ADVERTISE_100_BASE_T4 | - REG_PHY_AUTONEG_ADVERTISE_100_BASE_TX_FULL | + /* Selector Field: 0x1 = IEEE 802.3 */ + Data32 = 0x1; + Data32 |= REG_PHY_AUTONEG_ADVERTISE_100_BASE_TX_FULL | REG_PHY_AUTONEG_ADVERTISE_100_BASE_TX_HALF | REG_PHY_AUTONEG_ADVERTISE_10_BASE_T_FULL | REG_PHY_AUTONEG_ADVERTISE_10_BASE_T_HALF; + PhyWrite (PhyDriver, PAGE_PHY, REG_PHY_AUTONEG_ADVERTISE, Data32); PhyRead (PhyDriver, PAGE_PHY, REG_PHY_CONTROL, &Data32); From 6f88abf24bbf19bda9ede972ab80abcf3615a096 Mon Sep 17 00:00:00 2001 From: Parker Newman Date: Fri, 8 Sep 2023 10:26:05 -0400 Subject: [PATCH 3/3] fix: Fix KSZ9031 Idle Error Bug The KSZ9031 has a bug where it will stop working if the Idle Error count is maxed out. This can occur on power up on systems without ethernet magnetics and requires a soft reset if it occurs. This patch adds an Idle Error check and soft reset based on the Linux driver. Signed-off-by: Parker Newman --- Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c index 8d8ea03d89..b4a2d9abfb 100644 --- a/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c +++ b/Silicon/NVIDIA/Drivers/EqosDeviceDxe/PhyMicrel.c @@ -34,6 +34,8 @@ #define PHY_STATUS_LINK BIT2 +#define REG_PHY_1000T_STATUS 0x0A + #define PAGE_RGMII_TIMING 2 #define REG_PHY_CTRL_SKEW 0x4 @@ -256,7 +258,6 @@ PhyMicrelConfig ( IN PHY_DRIVER *PhyDriver ) { - //UINT32 Data32; EFI_STATUS Status; Status = PhyMicrelSetTimings(PhyDriver); @@ -283,6 +284,19 @@ PhyMicrelDetectLink ( UINT32 Data32; EFI_STATUS Status; + Status = PhyRead(PhyDriver, PAGE_PHY, REG_PHY_1000T_STATUS, &Data32); + if (EFI_ERROR(Status)) { + DEBUG ((DEBUG_ERROR, "Micrel: Failed to read 1000T_STATUS register\r\n")); + return; + } + + // If idle error maxed out the KSZ needs a reset + if((Data32 & 0xFF) == 0xFF){ + DEBUG ((DEBUG_ERROR, "Micrel: Idle error maxed, resetting\r\n")); + PhySoftReset(PhyDriver); + return; + } + Status = PhyRead(PhyDriver, PAGE_PHY, REG_PHY_STATUS, &Data32); if (EFI_ERROR(Status)) { DEBUG ((DEBUG_ERROR, "Micrel: Failed to read PHY_STATUS register\r\n"));