/** @file
  Reset Architectural Protocol implementation

 @copyright
  INTEL CONFIDENTIAL
  Copyright 2011 - 2017 Intel Corporation.

  The source code contained or described herein and all documents related to the
  source code ("Material") are owned by Intel Corporation or its suppliers or
  licensors. Title to the Material remains with Intel Corporation or its suppliers
  and licensors. The Material may contain trade secrets and proprietary and
  confidential information of Intel Corporation and its suppliers and licensors,
  and is protected by worldwide copyright and trade secret laws and treaty
  provisions. No part of the Material may be used, copied, reproduced, modified,
  published, uploaded, posted, transmitted, distributed, or disclosed in any way
  without Intel's prior express written permission.

  No license under any patent, copyright, trade secret or other intellectual
  property right is granted to or conferred upon you by disclosure or delivery
  of the Materials, either expressly, by implication, inducement, estoppel or
  otherwise. Any license under such intellectual property rights must be
  express and approved by Intel in writing.

  Unless otherwise agreed by Intel in writing, you may not remove or alter
  this notice or any other notice embedded in Materials by Intel or
  Intel's suppliers or licensors in any way.

  This file contains an 'Intel Peripheral Driver' and is uniquely identified as
  "Intel Reference Module" and is licensed for Intel CPUs and chipsets under
  the terms of your license agreement with Intel or your vendor. This file may
  be modified by the user, subject to additional terms of the license agreement.

@par Specification
**/
#include "ScReset.h"
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/ConfigBlockLib.h>
#include "CmosMap.h"
#include "ScAccess.h"
#include "HeciRegs.h"

SC_RESET_INSTANCE  *mResetInstance;

/**
  Install and initialize reset protocols.

  @param[in] ImageHandle               Image handle of the loaded driver
  @param[in] SystemTable               Pointer to the System Table

  @retval EFI_SUCCESS                   Thread can be successfully created
  @retval EFI_OUT_OF_RESOURCES          Cannot allocate protocol data structure
  @retval EFI_DEVICE_ERROR              Cannot create the timer service
**/
EFI_STATUS
EFIAPI
InitializeScReset (
  IN EFI_HANDLE        ImageHandle,
  IN EFI_SYSTEM_TABLE  *SystemTable
  )
{
  EFI_STATUS                        Status;
  EFI_HANDLE                        ResetHandle;
#if ((TIANO_RELEASE_VERSION != 0) && (EFI_SPECIFICATION_VERSION < 0x00020000))
  DXE_SC_PLATFORM_POLICY_PROTOCOL   *ScPlatformPolicy;
#endif
  EFI_EVENT                         Event;
  EFI_PEI_HOB_POINTERS              HobPtr;
  SC_POLICY_HOB                     *ScPolicy;
  SC_GENERAL_CONFIG                 *GeneralConfig;

  DEBUG ((DEBUG_INFO, "InitializeScReset() Start\n"));
  ResetHandle = NULL;
  ScPolicy    = NULL;

  //
  // Get SC Policy HOB.
  //
  Status = EFI_NOT_FOUND;
  ZeroMem (&HobPtr, sizeof(EFI_PEI_HOB_POINTERS));
  HobPtr.Guid = GetFirstGuidHob (&gScPolicyHobGuid);
  ASSERT (HobPtr.Guid != NULL);

  ScPolicy = GET_GUID_HOB_DATA (HobPtr.Guid);
  Status = GetConfigBlock ((VOID *) ScPolicy, &gScGeneralConfigGuid, (VOID *) &GeneralConfig);
  ASSERT_EFI_ERROR (Status);

  //
  // Allocate Runtime memory for the ScExtendedReset protocol instance.
  //
  mResetInstance = AllocateRuntimePool (sizeof (SC_RESET_INSTANCE));
  if (mResetInstance == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

#if ((TIANO_RELEASE_VERSION != 0) && (EFI_SPECIFICATION_VERSION < 0x00020000))
  //
  // Get the desired platform setup policy.
  //
  Status = gBS->LocateProtocol (&gDxePlatformPolicyProtocolGuid, NULL, (VOID **) &ScPlatformPolicy);
  ASSERT_EFI_ERROR (Status);

  //
  // Check whether the CapsuleVariableName is filled.
  //
  if (ScPlatformPolicy->CapsuleVariableName != NULL) {
    //
    // Allocate a runtime space and copy string from CapsuleVariableName
    //
    mResetInstance->CapsuleVariableName = AllocateRuntimePool (StrSize (ScPlatformPolicy->CapsuleVariableName));
    CopyMem (
      mResetInstance->CapsuleVariableName,
      ScPlatformPolicy->CapsuleVariableName,
      StrSize (ScPlatformPolicy->CapsuleVariableName)
      );
  }
#endif
  //
  // Install protocol interface
  //
  mResetInstance->Signature                     = RESET_SIGNATURE;
  mResetInstance->Handle                        = NULL;
  mResetInstance->ScExtendedResetProtocol.Reset = ScExtendedReset;
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &mResetInstance->Handle,
                  &gEfiExtendedResetProtocolGuid,
                  &mResetInstance->ScExtendedResetProtocol,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);
  mResetInstance->PmcBase = PMC_BASE_ADDRESS;

  mResetInstance->AcpiBar = (UINT16)PcdGet16(PcdScAcpiIoPortBaseAddress);

  mResetInstance->ResetSelect = (UINT8)GeneralConfig->ResetSelect;

  //
  // Make sure the Reset Architectural Protocol is not already installed in the system
  //
  ASSERT_PROTOCOL_ALREADY_INSTALLED (NULL, &gEfiResetArchProtocolGuid);

  //
  // Hook the runtime service table
  //
  SystemTable->RuntimeServices->ResetSystem = (EFI_RESET_SYSTEM) IntelScResetSystem;

  //
  // Now install the Reset RT AP on a new handle
  //
  Status = gBS->InstallMultipleProtocolInterfaces (
                  &ResetHandle,
                  &gEfiResetArchProtocolGuid,
                  NULL,
                  NULL
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // Install a notification to convert the SCReset driver to Virtual Addr
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_NOTIFY,
                  ScResetVirtualddressChangeEvent,
                  NULL,
                  &gEfiEventVirtualAddressChangeGuid,
                  &Event
                  );
  ASSERT_EFI_ERROR (Status);

  //
  // The Lib Deconstruct will automatically be called when entrypoint return error.
  //
  DEBUG ((DEBUG_INFO, "InitializeScReset() End\n"));

  return Status;
}

/**
  Set flag to indicate the reset is controlled reset or uncontrolled reset.

**/
VOID
NotifyControlledReset(
 VOID
 ) {
  //
  // The following magic value in CMOS register is used by DXE module "NullMemoryTest"
  // to differentiate a reset as Controlled or Uncontrolled.
  //
  IoWrite8(CmosIo_72, EFI_CMOS_CLEAN_RESET);
  IoWrite8(CmosIo_73, B_MAGIC_CLEAN_RESET_VALUE); //Signal a controlled Reset

}


#if ((TIANO_RELEASE_VERSION != 0) && (EFI_SPECIFICATION_VERSION < 0x00020000))
/**
  If need be, do any special reset required for capsules. For this
  implementation where we're called from the ResetSystem() api,
  just set our capsule variable and return to let the caller
  do a soft reset.

  @param[in] CapsuleDataPtr            Pointer to the capsule block descriptors
**/
VOID
CapsuleReset (
  IN UINTN   CapsuleDataPtr
  )
{
  UINT32  Data32;
  UINT32  Eflags;
  UINT16  AcpiBaseAddr;

  //
  // Check the CapsuleVariableName, asset if it is not filled.
  //
  ASSERT (mResetInstance->CapsuleVariableName != NULL);

  //
  // This implementation assumes we're using a variable for the capsule
  // data pointer.
  //
  EfiSetVariable (
    mResetInstance->CapsuleVariableName,
    &gEfiCapsuleVendorGuid,
    EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS,  ///< attributes
    sizeof (UINTN),
    (VOID *) &CapsuleDataPtr
    );

  //
  // Read the ACPI Base address
  //
  AcpiBaseAddr = (UINT16)PcdGet16(PcdScAcpiIoPortBaseAddress);
  ASSERT (AcpiBaseAddr != 0);

  //
  // Read the PM1_CNT register
  //
  Data32 = IoRead32((UINTN)(AcpiBaseAddr + R_SC_ACPI_IO_PM1_CNT));

  Data32 = (UINT32)((Data32 &~(B_SC_ACPI_IO_PM1_CNT_SLP_TYP + B_SC_ACPI_IO_PM1_CNT_SLP_EN)) | V_SC_ACPI_IO_PM1_CNT_S3);

  Eflags  = (UINT32) AsmReadEflags ();

  if ((Eflags & 0x200)) {
    DisableInterrupts ();
  }

  AsmWbinvd ();
  AsmWriteCr0 (AsmReadCr0 () | 0x060000000);

  IoWrite32 (
	  (UINTN)(AcpiBaseAddr + R_SC_ACPI_IO_PM1_CNT),
    (UINT32) Data32
    );

  Data32 = Data32 | B_SC_ACPI_IO_PM1_CNT_SLP_EN;

  IoWrite32 (
	  (UINTN)(AcpiBaseAddr + R_SC_ACPI_IO_PM1_CNT),
    (UINT32) Data32
    );

  if ((Eflags & 0x200)) {
    EnableInterrupts ();
  }
  //
  // Should not return
  //
  EFI_DEADLOOP ();
}
#endif

/**
  Generate system reset by Reset Control Register (IO Cf9h)
  Note: This reset is hooked to RuntimeServices->ResetSystem and will be called
        in OS environment and if any debug messages added then need to handle it
        carefully, else debug build cause to BSOD.
        Its recommended not to have debug messages over here.
  @param[in] ResetType                 Warm or cold
  @param[in] ResetStatus               Possible cause of reset
  @param[in] DataSize                  Size of ResetData in bytes
  @param[in] ResetData                 Optional Unicode string
**/
VOID
EFIAPI
IntelScResetSystem (
  IN EFI_RESET_TYPE   ResetType,
  IN EFI_STATUS       ResetStatus,
  IN UINTN            DataSize,
  IN VOID             *ResetData OPTIONAL
  )
{
  UINT8          InitialData;
  UINT8          OutputData;
  UINT16         AcpiBase;
  UINT16         Data16;
  UINT32         Data32;


  NotifyControlledReset();

  switch (ResetType) {
    case EfiResetWarm:
		InitialData = V_SC_RST_IO_CNT_HARDSTARTSTATE;
      OutputData  = mResetInstance->ResetSelect;
	  if (mResetInstance->ResetSelect == V_SC_RST_IO_CNT_FULLRESET){
      }
      break;

    case EfiResetCold:
		InitialData = V_SC_RST_IO_CNT_HARDSTARTSTATE;
		OutputData = V_SC_RST_IO_CNT_FULLRESET;
      break;

    case EfiResetShutdown:
      ScCommonOnResetCallback();
      AcpiBase = (UINT16)mResetInstance->AcpiBar;
      ///
      /// Then, GPE0_EN should be disabled to avoid any GPI waking up the system from S5
      ///
      Data32 = 0;

	  IoWrite32((UINTN)(AcpiBase + R_SC_ACPI_IO_GPE0a_EN), Data32);
      ///
      /// Clear Sleep SMI Status
      ///
	  IoWrite16(AcpiBase + R_SC_SMI_IO_STS,
		  (UINT16)(IoRead16(AcpiBase + R_SC_SMI_IO_STS) | B_SC_SMI_IO_STS_ON_SLP_EN));
      ///
      /// Clear Power Button Status
      ///
	  IoWrite16(AcpiBase + R_SC_ACPI_IO_PM1_STS, B_SC_ACPI_IO_PM1_STS_PWRBTN);

      ///
      /// Secondly, Power Button Status bit must be cleared
      ///
      /// Write a "1" to bit[8] of power button status register at
      /// (ABASE + PM1_STS) to clear this bit
      /// Clear it through SMI Status register
      ///
	  Data16 = B_SC_SMI_IO_STS_PM1_STS_REG;
	  IoWrite16((UINTN)(AcpiBase + R_SC_SMI_IO_STS), Data16);

      ///
      /// Finally, transform system into S5 sleep state
      ///
	  Data32 = IoRead32((UINTN)(AcpiBase + R_SC_ACPI_IO_PM1_CNT));

	  Data32 = (UINT32)((Data32 &~(B_SC_ACPI_IO_PM1_CNT_SLP_TYP + B_SC_ACPI_IO_PM1_CNT_SLP_EN)) | V_SC_ACPI_IO_PM1_CNT_S5);

	  IoWrite32((UINTN)(AcpiBase + R_SC_ACPI_IO_PM1_CNT), Data32);

	  Data32 = Data32 | B_SC_ACPI_IO_PM1_CNT_SLP_EN;

	  IoWrite32((UINTN)(AcpiBase + R_SC_ACPI_IO_PM1_CNT), Data32);
      //
      // Should not return
      //
      CpuDeadLoop();

      return ;

    default:
      return ;
  }


  IoWrite8 (
	  (UINTN)R_SC_RST_IO_CNT,
    (UINT8) InitialData
    );

  IoWrite8 (
	  (UINTN)R_SC_RST_IO_CNT,
    (UINT8) OutputData
    );

  //
  // Given we should have reset getting here would be bad
  //

  CpuDeadLoop();

  ASSERT (FALSE);
}

/**
  Execute SC Extended Reset from the host controller.

  @param[in] This                      Pointer to the EFI_SC_EXTENDED_RESET_PROTOCOL instance.
  @param[in] ScExtendedResetTypes      SC Extended Reset Types which includes PowerCycle, Globalreset.

  @retval EFI_SUCCESS                  Successfully completed.
  @retval EFI_INVALID_PARAMETER        If ResetType is invalid.
**/
EFI_STATUS
EFIAPI
ScExtendedReset (
  IN EFI_SC_EXTENDED_RESET_PROTOCOL   *This,
  IN SC_EXTENDED_RESET_TYPES          ScExtendedResetTypes
  )
{
  //
  // Check if the parameters are valid.
  //
  if ((ScExtendedResetTypes.PowerCycle == 0) && (ScExtendedResetTypes.GlobalReset == 0)) {
    return EFI_INVALID_PARAMETER;
  }

  NotifyControlledReset();

  if (ScExtendedResetTypes.GlobalReset == 1) {
	  MmioOr32(mResetInstance->PmcBase + R_SC_PMC_MEM_ETR, (UINT32)(B_SC_PMC_MEM_ETR_CF9GR));
  }
  IoWrite8((UINTN)R_SC_RST_IO_CNT, (UINT8)V_SC_RST_IO_CNT_FULLRESET);

  //
  // Waiting for system reset
  //
  CpuDeadLoop ();

  return EFI_SUCCESS;
}

/**
  Fixup internal data pointers so that the services can be called in virtual mode.

  @param[in]  Event                     The event registered.
  @param[in]  Context                   Event context. Not used in this event handler.
**/
VOID
EFIAPI
ScResetVirtualddressChangeEvent (
  IN EFI_EVENT        Event,
  IN VOID             *Context
  )
{
  EfiConvertPointer (0x0, (VOID **) &(mResetInstance->ScExtendedResetProtocol.Reset));
#if ((TIANO_RELEASE_VERSION != 0) && (EFI_SPECIFICATION_VERSION < 0x00020000))
  EfiConvertPointer (0x0, (VOID **) &(mResetInstance->CapsuleVariableName));
#endif
  EfiConvertPointer (0x0, (VOID **) &(mResetInstance->AcpiBar));
  EfiConvertPointer (0x0, (VOID **) &(mResetInstance->PmcBase));
  EfiConvertPointer (0x0, (VOID **) &(mResetInstance));
}
