/** @file
  This file contains GPIO routines for RC usage

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

**/
#include <Base.h>
#include <Uefi/UefiBaseType.h>
#include <Library/IoLib.h>
#include <Library/DebugLib.h>
#include <Library/BaseMemoryLib.h>
#include <PchAccess.h>
#include <Library/GpioLib.h>
#include <Library/GpioNativeLib.h>
#include <Library/GpioPrivateLib.h>
#include <Library/PchCycleDecodingLib.h>
#include <Pch/Library/PeiDxeSmmGpioLib/GpioLibrary.h>
/**
  This procedure will get value of selected gpio register

  @param[in]  Group               GPIO group number
  @param[in]  Offset              GPIO register offset
  @param[out] RegVal              Value of gpio register

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioGetReg (
  IN  GPIO_GROUP              Group,
  IN  UINT32                  Offset,
  OUT UINT32                  *RegVal
  )
{
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINTN                GroupIndex;

  GroupIndex = GpioGetGroupIndexFromGroup (Group);
  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);
  //
  // Check if group argument exceeds GPIO GROUP INFO array
  //
  if ((UINTN) GroupIndex >= GpioGroupInfoLength) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  *RegVal = MmioRead32 (PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, Offset));

  return EFI_SUCCESS;
}

/**
  This procedure will set value of selected gpio register

  @param[in] Group               GPIO group number
  @param[in] Offset              GPIO register offset
  @param[in] RegVal              Value of gpio register

  @retval EFI_SUCCESS            The function completed successfully
  @retval EFI_INVALID_PARAMETER  Invalid group or pad number
**/
EFI_STATUS
GpioSetReg (
  IN GPIO_GROUP              Group,
  IN UINT32                  Offset,
  IN UINT32                  RegVal
  )
{
  GPIO_GROUP_INFO      *GpioGroupInfo;
  UINTN                GpioGroupInfoLength;
  UINTN                GroupIndex;

  GroupIndex = GpioGetGroupIndexFromGroup (Group);
  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);
  //
  // Check if group argument exceeds GPIO GROUP INFO array
  //
  if ((UINTN) GroupIndex >= GpioGroupInfoLength) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  MmioWrite32 (PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, Offset), RegVal);

  return EFI_SUCCESS;
}

/**
  This procedure is used by PchSmiDispatcher and will return information
  needed to register GPI SMI. Relation between Index and GpioPad number is:
  Index = GpioGroup + 24 * GpioPad

  @param[in]  Index               GPI SMI number
  @param[out] GpioPin             GPIO pin
  @param[out] GpiSmiBitOffset     GPI SMI bit position within GpiSmi Registers
  @param[out] GpiSmiEnRegAddress  Address of GPI SMI Enable register
  @param[out] GpiSmiStsRegAddress Address of GPI SMI status register

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioGetPadAndSmiRegs (
  IN UINT32            Index,
  OUT GPIO_PAD         *GpioPin,
  OUT UINT8            *GpiSmiBitOffset,
  OUT UINT32           *GpiSmiEnRegAddress,
  OUT UINT32           *GpiSmiStsRegAddress
  )
{
  UINT32               GroupIndex;
  UINT32               PadNumber;
  GPIO_GROUP_INFO      *GpioGroupInfo;
  GPIO_GROUP           GpioGroupOffset;
  UINTN                GpioGroupInfoLength;
  UINT32               SmiRegOffset;

  GPIO_PAD_OWN       PadOwnVal;
  UINT32             HostOwnVal;


  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);

  GpioGroupOffset = GpioGetLowestGroup ();

  PadNumber = 0;
  GroupIndex = 0;
  for (GroupIndex = 0; GroupIndex < GpioGroupInfoLength; GroupIndex++) {
    PadNumber = Index;
    if (PadNumber < GpioGroupInfo[GroupIndex].PadPerGroup) {
      //
      // Found group and pad number
      //
      break;
    }
    Index = Index - GpioGroupInfo[GroupIndex].PadPerGroup;
  }

  //
  // Check if legal pad number
  //
  if (PadNumber >= GpioGroupInfo[GroupIndex].PadPerGroup){
    return EFI_INVALID_PARAMETER;
  }

  //
  // Check if selected group has GPI SMI Enable and Status registers
  //
  if (GpioGroupInfo[GroupIndex].SmiEnOffset == NO_REGISTER_FOR_PROPERTY) {
    return EFI_INVALID_PARAMETER;
  }

  DEBUG_CODE_BEGIN ();
  //
  // Check if selected GPIO Pad is not owned by CSME/ISH/IE
  //
  GpioGetPadOwnership (GpioGetGpioPadFromGroupAndPadNumber (GroupIndex + GpioGroupOffset, PadNumber), &PadOwnVal);
  if (PadOwnVal != GpioPadOwnHost) {
    DEBUG ((DEBUG_ERROR, "GPIO ERROR: Accessing pad not owned by host (Group=%d, Pad=%d)!\n",GroupIndex, PadNumber));
    return EFI_INVALID_PARAMETER;
  }
  //
  // Check if Host Software Pad Ownership is set to ACPI Mode
  //
  GpioGetHostSwOwnershipForPad (GpioGetGpioPadFromGroupAndPadNumber (GroupIndex + GpioGroupOffset, PadNumber), &HostOwnVal);
  if (HostOwnVal != V_PCH_PCR_GPIO_HOSTSW_OWN_ACPI) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  DEBUG_CODE_END ();

  *GpioPin = GpioGetGpioPadFromGroupAndPadNumber (GroupIndex + GpioGroupOffset, PadNumber);
  *GpiSmiBitOffset = (UINT8)(PadNumber % 32);

  SmiRegOffset = GpioGroupInfo[GroupIndex].SmiEnOffset + (PadNumber / 32) * 0x4;
  *GpiSmiEnRegAddress = PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, SmiRegOffset);

  SmiRegOffset = GpioGroupInfo[GroupIndex].SmiStsOffset + (PadNumber / 32) * 0x4;
  *GpiSmiStsRegAddress = PCH_PCR_ADDRESS (GpioGroupInfo[GroupIndex].Community, SmiRegOffset);

  return EFI_SUCCESS;
}

/**
  This procedure will clear GPIO_UNLOCK_SMI_STS

  @param[in]  None

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
GpioClearUnlockSmiSts (
  VOID
  )
{
  UINT16  AcpiBaseAddr;

  PchAcpiBaseGet (&AcpiBaseAddr);

  //
  // GPIO_UNLOCK_SMI_STS is cleared by writing 1 to it. Other bits in
  // R_PCH_SMI_STS registers are either RO or RW/1C so writing 0 to them
  // will not change their state.
  //
  IoWrite32 (AcpiBaseAddr + R_PCH_SMI_STS, B_PCH_SMI_STS_GPIO_UNLOCK);

  return EFI_SUCCESS;
}


/**
  This procedure will set GPIO Driver IRQ number

  @param[in]  Irq                 Irq number

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid IRQ number
**/
EFI_STATUS
GpioSetIrq (
  IN  UINT8          Irq
  )
{
  UINT32   Data32And;
  UINT32   Data32Or;

  //
  // Check if Irq is 14 or 15
  //
  if ((Irq < 14) || (Irq > 15)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  Data32And = (UINT32) ~(B_PCH_PCR_GPIO_MISCCFG_IRQ_ROUTE);
  Data32Or  = (UINT32) (Irq - 14) << N_PCH_PCR_GPIO_MISCCFG_IRQ_ROUTE;

  //
  // Program MISCCFG register for Community 0
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM0, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 1
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM1, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 2
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM2, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  //
  // Program MISCCFG register for Community 3
  //
  MmioAndThenOr32 (
    PCH_PCR_ADDRESS (PID_GPIOCOM3, R_PCH_PCR_GPIO_MISCCFG),
    Data32And,
    Data32Or
    );

  return EFI_SUCCESS;
}

/**
  This procedure will perform special handling of GPP_A_12 on PCH-LP.

  @param[in]  None

  @retval None
**/
VOID
GpioA12SpecialHandling (
  VOID
  )
{
  GPIO_PAD_OWN         PadOwnVal;

  //
  // SKL PCH BWG 16.6. PCH-LP GPP_A_12 Special Handling
  //
  if (GetPchSeries () == PchLp) {
    GpioGetPadOwnership (GPIO_SKL_LP_GPP_A12, &PadOwnVal);

    //
    // If the pad is host-own, BIOS has to always lock this pad after being initialized
    //
    if (PadOwnVal == GpioPadOwnHost) {
      //
      // Set PadCfgLock for GPP_A_12
      //
      GpioLockPadCfg (GPIO_SKL_LP_GPP_A12);
    }
  }
}
