/** @file
  PCH LAN Sx handler implementation.

Copyright (c) 2017, Intel Corporation. All rights reserved.<BR>
SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <Library/TimerLib.h>
#include "PchInitSmm.h"

//
// Maximum loop time for GbE status check
//
#define GBE_MAX_LOOP_TIME  4000

/**
  Checks if Lan is Enabled or Disabled

  @retval  BOOLEAN    TRUE if device is enabled, FALSE otherwise.
**/
BOOLEAN
IsGbeEnabled (
  VOID
  )
{
  UINTN  GbePciBase;

  GbePciBase = MmPciBase (
                 DEFAULT_PCI_BUS_NUMBER_PCH,
                 PCI_DEVICE_NUMBER_PCH_LAN,
                 PCI_FUNCTION_NUMBER_PCH_LAN
                 );

  if (MmioRead32 (GbePciBase) != 0xFFFFFFFF) {
    return TRUE;
  }

  return FALSE;
}



/**
  Test for MDIO operation complete.

  @param [in]  GbeBar   GbE MMIO space

  @retval EFI_SUCCESS
  @retval EFI_TIMEOUT
**/
EFI_STATUS
GbeMdiWaitReady (
  UINT32          GbeBar
  )
{
  UINT32  Count;

  for (Count = 0; Count < GBE_MAX_LOOP_TIME; ++Count) {
    if (MmioRead32 (GbeBar + R_PCH_LAN_CSR_MDIC) & B_PCH_LAN_CSR_MDIC_RB) {
      return EFI_SUCCESS;
    }
    MicroSecondDelay (50);
  }
  return EFI_TIMEOUT;
}

/**
  Acquire MDIO software semaphore.

  1. Ensure that MBARA offset F00h [5] = 1b
  2. Poll MBARA offset F00h [5] up to 200ms

  @param [in] GbeBar   GbE MMIO space

  @retval EFI_SUCCESS
  @retval EFI_TIMEOUT
**/
EFI_STATUS
GbeAcquireMdio (
  IN      UINT32  GbeBar
  )
{
  UINT32  ExtCnfCtrl;
  UINT32  Count;

  MmioOr32 (GbeBar + R_PCH_LAN_CSR_EXTCNF_CTRL, B_PCH_LAN_CSR_EXTCNF_CTRL_SWFLAG);
  for (Count = 0; Count < GBE_MAX_LOOP_TIME; ++Count) {
    ExtCnfCtrl = MmioRead32 (GbeBar + R_PCH_LAN_CSR_EXTCNF_CTRL);
    if (ExtCnfCtrl & B_PCH_LAN_CSR_EXTCNF_CTRL_SWFLAG) {
      return EFI_SUCCESS;
    }
    MicroSecondDelay (50);
  }
  ASSERT (FALSE);
  return EFI_TIMEOUT;
}


/**
  Release MDIO software semaphore by clearing MBARA offset F00h [5]

  @param [in]  GbeBar   GbE MMIO space
**/
VOID
GbeReleaseMdio (
  IN      UINT32  GbeBar
  )
{
  ASSERT (MmioRead32 (GbeBar + R_PCH_LAN_CSR_EXTCNF_CTRL) & B_PCH_LAN_CSR_EXTCNF_CTRL_SWFLAG);
  MmioAnd32 (GbeBar + R_PCH_LAN_CSR_EXTCNF_CTRL, (UINT32) ~B_PCH_LAN_CSR_EXTCNF_CTRL_SWFLAG);
  ASSERT ((MmioRead32 (GbeBar + R_PCH_LAN_CSR_EXTCNF_CTRL) & B_PCH_LAN_CSR_EXTCNF_CTRL_SWFLAG) == 0);
}

/**
  Perform MDI write.

  @param [in]  GbeBar   GbE MMIO space
  @param [in]  Data     Value to write in lower 16bits, upper 16bit may contain opcode.

  @return EFI_STATUS
**/
EFI_STATUS
GbeMdiWrite (
  IN      UINT32  GbeBar,
  IN      UINT32  Data
  )
{
  MmioWrite32 (GbeBar + R_PCH_LAN_CSR_MDIC, Data | 0x04000000);
  return GbeMdiWaitReady (GbeBar);
}

/**
  Perform MDI read.

  @param [in]  GbeBar   GbE MMIO space
  @param [in]  Command  MDI command
  @param [out] Data     Value read

  @return EFI_STATUS
**/
EFI_STATUS
GbeMdiRead (
  IN      UINT32  GbeBar,
  IN      UINT32  Command,
  OUT     UINT16  *Data
  )
{
  EFI_STATUS  Status;
  MmioWrite32 (GbeBar + R_PCH_LAN_CSR_MDIC, Command | 0x08000000);
  Status = GbeMdiWaitReady (GbeBar);
  *Data = (UINT16) MmioRead32 (GbeBar + R_PCH_LAN_CSR_MDIC);
  return Status;
}

/**
  Configure WOL during Sx entry.

  @param [in]  GbeBar   GbE MMIO space
**/
VOID
GbeWolWorkaround (
  IN      UINT32  GbeBar
  )
{
  UINT32      RAL0;
  UINT32      RAH0;
  EFI_STATUS  Status;
  UINT16      Data16;

  //
  // System BIOS performs read from MBARA offset 5400h [31:0] and MBARA offset 5404h [31:0]
  //
  RAL0 = MmioRead32 (GbeBar + R_PCH_LAN_CSR_RAL);
  RAH0 = MmioRead32 (GbeBar + R_PCH_LAN_CSR_RAH);

  //
  // Set MBARA offset 20h = 0x043f6400
  //
  Status = GbeMdiWrite (GbeBar, 0x043f6400);

  //
  // If timeout is reached, force MAC to SMB and try again
  // Assert if failed on second attempt
  //
  if (Status == EFI_TIMEOUT) {
    //
    // Change Extended Device Control Register BIT 11 to 1 which
    // forces the interface between the MAC and the Phy to be on SMBus.
    // Cleared on the assertion of PCI reset.
    //
    MmioOr32 (GbeBar + R_PCH_LAN_CSR_CTRL_EXT, B_PCH_LAN_CSR_CTRL_EXT_FORCE_SMB);
    Status = GbeMdiWrite (GbeBar, 0x043f6400);
  }
  if (EFI_ERROR (Status)) return;

  //
  // Wait 4 mSec
  //
  MicroSecondDelay (4000);

  //
  // Set MBARA offset 20h = 0x4310010
  //
  Status = GbeMdiWrite (GbeBar, 0x04310010);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4320000 or with
  // the least significant word of MBARA offset 5400
  //
  Status = GbeMdiWrite (GbeBar, (0x04320000 | (RAL0 & 0xFFFF)));
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4310011
  //
  Status = GbeMdiWrite (GbeBar, 0x04310011);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4320000 or with
  // the most significant word of MBARA offset 5400
  //
  Status = GbeMdiWrite (GbeBar, (0x04320000 | (RAL0 >> 16)));
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4310012
  //
  Status = GbeMdiWrite (GbeBar, 0x04310012);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4320000 or with
  // the least significant word of MBARA offset 5404
  //
  Status = GbeMdiWrite (GbeBar, (0x04320000 | (RAH0 & B_PCH_LAN_CSR_RAH_RAH)));
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4310013
  //
  Status = GbeMdiWrite (GbeBar, 0x04310013);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4328000
  //
  Status = GbeMdiWrite (GbeBar, 0x04328000);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4310001
  //
  Status = GbeMdiWrite (GbeBar, 0x04310001);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x8320000
  //
  Status = GbeMdiRead (GbeBar, 0x08320000, &Data16);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x4320000 OR TEMP[15:0] OR 1
  //
  Status = GbeMdiWrite (GbeBar, 0x04320000 | Data16 | BIT0);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x43f6460
  //
  Status = GbeMdiWrite (GbeBar, 0x043f6460);
  if (EFI_ERROR (Status)) return;

  //
  // Wait 4 mSec
  //
  MicroSecondDelay (4000);

  //
  // Set MBARA offset 20h = 0x4310042
  //
  Status = GbeMdiWrite (GbeBar, 0x04310042);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA offset 20h = 0x43F6020
  //
  Status = GbeMdiWrite (GbeBar, 0x043F6020);
  if (EFI_ERROR (Status)) return;

  //
  // Wait 4 mSec
  //
  MicroSecondDelay (4000);

  //
  // Set MBARA offset 20h = 0x8310000
  // TEMP[15:0] = MBARA + 20[15:0]
  //
  Status = GbeMdiRead (GbeBar, 0x08310000, &Data16);
  if (EFI_ERROR (Status)) return;

  //
  // Set MBARA + 20h = 4310000h or with the TEMP[15:0] OR 10h
  //
  GbeMdiWrite (GbeBar, 0x04310000 | Data16 | BIT4);
}

/**
  Additional Internal GbE Controller special cases WOL Support.

  System BIOS is required perform additional steps upon S0 to S3,4,5 transition
  when ME is off and GbE device in D0. This is needed to enable LAN wake
  in particular when platform is shut-down from EFI.
**/
VOID
GbeSxWorkaround (
  VOID
  )
{
  UINTN       LanRegBase;
  UINT32      GbeBar;
  EFI_STATUS  Status;

  LanRegBase = MmPciBase (
                 DEFAULT_PCI_BUS_NUMBER_PCH,
                 PCI_DEVICE_NUMBER_PCH_LAN,
                 PCI_FUNCTION_NUMBER_PCH_LAN
                 );

  if (MmioRead16 (LanRegBase + PCI_VENDOR_ID_OFFSET) == 0xFFFF) {
    return;
  }

  //
  // Check if GbE device is in D0
  //
  if ((MmioRead16 (LanRegBase + R_PCH_LAN_PMCS) & B_PCH_LAN_PMCS_PS) != V_PCH_LAN_PMCS_PS0) {
    return;
  }

  ASSERT (mResvMmioSize >= (1 << N_PCH_LAN_MBARA_ALIGN));
  GbeBar = (UINT32) mResvMmioBaseAddr;
  if (GbeBar == 0) {
    ASSERT (FALSE);
    return;
  }

  //
  // Enable MMIO decode using reserved range.
  //
  MmioAnd16 (LanRegBase + PCI_COMMAND_OFFSET, (UINT16) ~EFI_PCI_COMMAND_MEMORY_SPACE);
  MmioWrite32 (LanRegBase + R_PCH_LAN_MBARA, GbeBar);
  MmioOr16 (LanRegBase + PCI_COMMAND_OFFSET, EFI_PCI_COMMAND_MEMORY_SPACE);

  //
  // If MBARA offset 5800h [0] = 1b then proceed with the w/a
  //
  if (MmioRead32 (GbeBar + R_PCH_LAN_CSR_WUC) & B_PCH_LAN_CSR_WUC_APME) {
    Status = GbeAcquireMdio (GbeBar);
    ASSERT_EFI_ERROR (Status);
    if (!EFI_ERROR (Status)) {
      GbeWolWorkaround (GbeBar);
      GbeReleaseMdio (GbeBar);
    }
  }

  //
  // Disable MMIO decode.
  //
  MmioAnd16 (LanRegBase + PCI_COMMAND_OFFSET, (UINT16) ~EFI_PCI_COMMAND_MEMORY_SPACE);
  MmioWrite32 (LanRegBase + R_PCH_LAN_MBARA, 0);
}

/**
  Enable platform wake from LAN when in DeepSx if platform supports it.
  Called upon Sx entry.
**/
VOID
GbeConfigureDeepSxWake (
  VOID
  )
{
  UINT32      PchPwrmBase;

  PchPwrmBaseGet (&PchPwrmBase);

  if ((MmioRead32 (PchPwrmBase + R_PCH_PWRM_DSX_CFG) & (UINT32) B_PCH_PWRM_DSX_CFG_LAN_WAKE_EN) != 0) {
    IoOr32 ((UINTN) (mAcpiBaseAddr + R_PCH_ACPI_GPE0_EN_127_96), (UINT32) B_PCH_ACPI_GPE0_EN_127_96_LAN_WAKE);
  }
}

/**
  GbE Sx entry handler
**/
VOID
PchLanSxCallback (
  VOID
  )
{
  if (IsGbeEnabled ()) {
    GbeSxWorkaround ();
    GbeConfigureDeepSxWake ();

  }
}
