/** @file
  This file contains routines for GPIO native and chipset specific usage

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

**/
#include "GpioLibrary.h"

//
// Chipset specific data
//
//SerialIo
extern GPIO_PAD_NATIVE_FUNCTION mPchLpI2cGpio[PCH_LP_SERIALIO_MAX_I2C_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER];
extern GPIO_PAD_NATIVE_FUNCTION mPchHI2cGpio[PCH_H_SERIALIO_MAX_I2C_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER];
extern GPIO_PAD_NATIVE_FUNCTION mPchLpUartGpio[PCH_SERIALIO_MAX_UART_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_UART_CONTROLLER];
extern GPIO_PAD_NATIVE_FUNCTION mPchHUartGpio[PCH_SERIALIO_MAX_UART_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_UART_CONTROLLER];
extern GPIO_PAD_NATIVE_FUNCTION mPchLpSpiGpio[PCH_SERIALIO_MAX_SPI_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER];
extern GPIO_PAD_NATIVE_FUNCTION mPchHSpiGpio[PCH_SERIALIO_MAX_SPI_CONTROLLERS][PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER];

//SATA
extern GPIO_PAD_NATIVE_FUNCTION mPchLpSataPortResetToGpioMap[PCH_LP_AHCI_MAX_PORTS];
extern GPIO_PAD_NATIVE_FUNCTION mPchHSataPortResetToGpioMap[PCH_H_AHCI_MAX_PORTS];
extern GPIO_PAD_NATIVE_FUNCTION mPchLpSataDevSlpPinToGpioMap[PCH_LP_AHCI_MAX_PORTS];
extern GPIO_PAD_NATIVE_FUNCTION mPchHSataDevSlpPinToGpioMap[PCH_H_AHCI_MAX_PORTS];

//
// SKL specific
//
extern GPIO_GROUP_INFO mPchLpGpioGroupInfo[V_PCH_LP_GPIO_GROUP_MAX];
extern GPIO_GROUP_INFO mPchHGpioGroupInfo[V_PCH_H_GPIO_GROUP_MAX];

/**
  This procedure will set GPIO mode

  @param[in]  GpioPad             GPIO pad
  @param[out] PadModeValue        GPIO pad mode value

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid group or pad number
**/
EFI_STATUS
SetGpioPadMode (
  IN GPIO_PAD                GpioPad,
  IN GPIO_PAD_MODE           PadModeValue
  )
{
  UINT32               PadCfgOrMask;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  if (PadModeValue != (GPIO_PAD_MODE)GpioHardwareDefault) {

    PadCfgOrMask = (((PadModeValue & B_GPIO_PAD_MODE_MASK) >> (N_GPIO_PAD_MODE_BIT_POS + 1)) << N_PCH_GPIO_PAD_MODE);

    GpioWritePadCfgReg (
      GpioPad,
      0,
      (UINT32)~B_PCH_GPIO_PAD_MODE,
      PadCfgOrMask
      );
  }

  return EFI_SUCCESS;
}

/**
  This procedure will get GPIO mode

  @param[in]  GpioPad             GPIO pad
  @param[out] PadModeValue        GPIO pad mode value

  @retval EFI_SUCCESS             The function completed successfully
  @retval EFI_INVALID_PARAMETER   Invalid GpioPad
**/
EFI_STATUS
GetGpioPadMode (
  IN  GPIO_PAD                 GpioPad,
  OUT GPIO_PAD_MODE            *PadModeValue
  )
{
  UINT32        PadCfgRegValue;

  if (!GpioIsPadValid (GpioPad)) {
    ASSERT (FALSE);
    return EFI_INVALID_PARAMETER;
  }

  if (!GpioIsPadHostOwned (GpioPad)) {
    return EFI_UNSUPPORTED;
  }

  GpioReadPadCfgReg (
    GpioPad,
    0,
    &PadCfgRegValue
    );

  *PadModeValue = (GPIO_PAD_MODE)(((PadCfgRegValue & B_PCH_GPIO_PAD_MODE) >> (N_PCH_GPIO_PAD_MODE - (N_GPIO_PAD_MODE_BIT_POS + 1))) | (0x1 << N_GPIO_PAD_MODE_BIT_POS));

  return EFI_SUCCESS;
}


/**
  This procedure will retrieve address and length of GPIO info table

  @param[out]  GpioGroupInfoTableLength   Length of GPIO group table

  @retval Pointer to GPIO group table

**/
GPIO_GROUP_INFO*
GpioGetGroupInfoTable (
  OUT UINTN               *GpioGroupInfoTableLength
  )
{
  if (GetPchSeries () == PchLp) {
    *GpioGroupInfoTableLength = sizeof (mPchLpGpioGroupInfo) / sizeof (GPIO_GROUP_INFO);
    return mPchLpGpioGroupInfo;
  } else {
    *GpioGroupInfoTableLength = sizeof (mPchHGpioGroupInfo) / sizeof (GPIO_GROUP_INFO);
    return mPchHGpioGroupInfo;
  }
}


/**
  This procedure is used to check if GpioPad is valid for certain chipset

  @param[in]  GpioPad             GPIO pad

  @retval TRUE                    This pin is valid on this chipset
          FALSE                   Incorrect pin
**/
BOOLEAN
GpioIsCorrectPadForThisChipset (
  IN  GPIO_PAD        GpioPad
  )
{
  PCH_SERIES PchSeries;

  PchSeries = GetPchSeries ();

  if ((PchSeries == PchH) && (GPIO_GET_CHIPSET_ID (GpioPad) == GPIO_SKL_H_CHIPSET_ID)) {
    return TRUE;
  } else if ((PchSeries == PchLp) && (GPIO_GET_CHIPSET_ID (GpioPad) == GPIO_SKL_LP_CHIPSET_ID)) {
    return TRUE;
  }

  return FALSE;
}


/**
  This procedure will get number of pads for certain GPIO group

  @param[in] Group            GPIO group number

  @retval Value               Pad number for group
                              If illegal group number then return 0
**/
UINT32
GpioGetPadPerGroup (
  IN GPIO_GROUP      Group
  )
{
  GPIO_GROUP_INFO     *GpioGroupInfo;
  UINTN               GpioGroupInfoLength;
  UINT32              GroupIndex;
  //
  // Check if group argument exceeds GPIO GROUP INFO array
  //
  GpioGroupInfo = GpioGetGroupInfoTable (&GpioGroupInfoLength);
  GroupIndex = GpioGetGroupIndexFromGroup (Group);

  if ((UINTN) GroupIndex >= GpioGroupInfoLength) {
    ASSERT (FALSE);
    return 0;
  } else {
    return GpioGroupInfo[GroupIndex].PadPerGroup;
  }
}

/**
  This procedure will get number of groups

  @param[in] none

  @retval Value               Group number
**/
UINT8
GpioGetNumberOfGroups (
  VOID
  )
{
  if (GetPchSeries () == PchLp) {
    return V_PCH_LP_GPIO_GROUP_MAX;
  } else {
    return V_PCH_H_GPIO_GROUP_MAX;
  }
}
/**
  This procedure will get lowest group

  @param[in] none

  @retval Value               Lowest Group
**/
GPIO_GROUP
GpioGetLowestGroup (
  VOID
  )
{
  if (GetPchSeries () == PchLp) {
    return (UINT32) GPIO_SKL_LP_GROUP_GPP_A;
  } else {
    return (UINT32) GPIO_SKL_H_GROUP_GPP_A;
  }
}
/**
  This procedure will get highest group

  @param[in] none

  @retval Value               Highest Group
**/
GPIO_GROUP
GpioGetHighestGroup (
  VOID
  )
{
  if (GetPchSeries () == PchLp) {
    return (UINT32) GPIO_SKL_LP_GROUP_GPD;
  } else {
    return (UINT32) GPIO_SKL_H_GROUP_GPD;
  }
}

/**
  This procedure will get group number

  @param[in] GpioPad          Gpio Pad

  @retval Value               Group number
**/
GPIO_GROUP
GpioGetGroupFromGpioPad (
  IN GPIO_PAD         GpioPad
  )
{
  return GPIO_GET_GROUP_FROM_PAD (GpioPad);
}

/**
  This procedure will get group index (0 based)

  @param[in] GpioPad          Gpio Pad

  @retval Value               Group Index
**/
UINT32
GpioGetGroupIndexFromGpioPad (
  IN GPIO_PAD        GpioPad
  )
{
  return (UINT32) GPIO_GET_GROUP_INDEX_FROM_PAD (GpioPad);
}

/**
  This procedure will get group index (0 based) from group

  @param[in] GpioGroup        Gpio Group

  @retval Value               Group Index
**/
UINT32
GpioGetGroupIndexFromGroup (
  IN GPIO_GROUP        GpioGroup
  )
{
  return (UINT32) GPIO_GET_GROUP_INDEX (GpioGroup);
}

/**
  This procedure will get pad number (0 based) from Gpio Pad

  @param[in] GpioPad          Gpio Pad

  @retval Value               Pad Number
**/
UINT32
GpioGetPadNumberFromGpioPad (
  IN GPIO_PAD        GpioPad
  )
{
  return (UINT32) GPIO_GET_PAD_NUMBER (GpioPad);
}
/**
  This procedure will return GpioPad from Group and PadNumber

  @param[in] Group              GPIO group
  @param[in] PadNumber          GPIO PadNumber

  @retval GpioPad               GpioPad
**/
GPIO_PAD
GpioGetGpioPadFromGroupAndPadNumber (
  IN GPIO_GROUP      Group,
  IN UINT32          PadNumber
  )
{
  if (GetPchSeries () == PchLp) {
    return GPIO_PAD_DEF (Group,PadNumber);
  } else {
    return GPIO_PAD_DEF (Group,PadNumber);
  }
}

/**
  This procedure will return GpioPad from GroupIndex and PadNumber

  @param[in] GroupIndex         GPIO GroupIndex
  @param[in] PadNumber          GPIO PadNumber

  @retval GpioPad               GpioPad
**/
GPIO_PAD
GpioGetGpioPadFromGroupIndexAndPadNumber (
  IN UINT32          GroupIndex,
  IN UINT32          PadNumber
  )
{
  GPIO_GROUP Group;

  if (GetPchSeries () == PchLp) {
    Group = GPIO_GROUP_DEF (GroupIndex, GPIO_SKL_LP_CHIPSET_ID);
    return GPIO_PAD_DEF (Group,PadNumber);
  } else {
    Group = GPIO_GROUP_DEF (GroupIndex, GPIO_SKL_H_CHIPSET_ID);
    return GPIO_PAD_DEF (Group,PadNumber);
  }
}


/**
  This function sets SerialIo I2C controller pins into native mode

  @param[in]  SerialIoI2cControllerNumber   I2C controller

  @retval Status
**/
EFI_STATUS
GpioSetSerialIoI2cPinsIntoNativeMode (
  IN  UINT32            SerialIoI2cControllerNumber
  )
{
  EFI_STATUS               Status;
  UINTN                    Index;
  GPIO_PAD_NATIVE_FUNCTION (*I2cGpio) [PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER];

  Status = EFI_SUCCESS;

  if (GetPchSeries () == PchLp) {
    ASSERT (SerialIoI2cControllerNumber < PCH_LP_SERIALIO_MAX_I2C_CONTROLLERS);
    I2cGpio = mPchLpI2cGpio;
  } else {
    ASSERT (SerialIoI2cControllerNumber < PCH_H_SERIALIO_MAX_I2C_CONTROLLERS);
    I2cGpio = mPchHI2cGpio;
  }

  for (Index = 0; Index < PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER; Index++) {
    Status = SetGpioPadMode (I2cGpio[SerialIoI2cControllerNumber][Index].Pad, I2cGpio[SerialIoI2cControllerNumber][Index].Mode);
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
    GpioSetInputInversion (I2cGpio[SerialIoI2cControllerNumber][Index].Pad, 0);
  }
  return Status;
}

/**
  This function sets SerialIo I2C controller pins tolerance

  @param[in]  SerialIoI2CControllerNumber   I2C controller
  @param[in]  Pad1v8Tolerance               TRUE:  Enable  1v8 Pad tolerance
                                            FALSE: Disable 1v8 Pad tolerance

  @retval Status
**/
EFI_STATUS
GpioSetSerialIoI2CPinsTolerance (
  IN  UINT32            SerialIoI2cControllerNumber,
  IN  BOOLEAN           Pad1v8Tolerance
  )
{
  EFI_STATUS               Status;
  UINTN                    Index;
  GPIO_PAD_OWN             PadOwnership;
  GPIO_PAD_NATIVE_FUNCTION (*I2cGpio) [PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER];
  GPIO_CONFIG              GpioData;

  Status = EFI_SUCCESS;

  if (GetPchSeries () == PchLp) {
    ASSERT (SerialIoI2cControllerNumber < PCH_LP_SERIALIO_MAX_I2C_CONTROLLERS);
    I2cGpio = mPchLpI2cGpio;
  } else {
    ASSERT (SerialIoI2cControllerNumber < PCH_H_SERIALIO_MAX_I2C_CONTROLLERS);
    I2cGpio = mPchHI2cGpio;
  }

  ZeroMem (&GpioData, sizeof (GPIO_CONFIG));
  if (Pad1v8Tolerance) {
    GpioData.ElectricalConfig = GpioTolerance1v8;
  } else {
    GpioData.ElectricalConfig = GpioNoTolerance1v8;
  }
  for (Index = 0; Index < PCH_SERIAL_IO_PINS_PER_I2C_CONTROLLER; Index++) {
    GpioGetPadOwnership (I2cGpio[SerialIoI2cControllerNumber][Index].Pad , &PadOwnership);
    if (PadOwnership == GpioPadOwnHost) {
      Status = GpioSetPadConfig (I2cGpio[SerialIoI2cControllerNumber][Index].Pad, &GpioData);
    } else {
      return EFI_UNSUPPORTED;
    }
  }
  return Status;
}


/**
  This function sets SerialIo UART controller pins into native mode

  @param[in]  SerialIoI2CControllerNumber   UART controller
  @param[in]  HardwareFlowControl           Hardware Flow control

  @retval Status
**/
EFI_STATUS
GpioSetSerialIoUartPinsIntoNativeMode (
  IN  UINT32            SerialIoUartControllerNumber,
  IN  BOOLEAN           HardwareFlowControl
  )
{
  EFI_STATUS    Status;
  UINTN         Index;
  UINTN         PinsUsed;
  GPIO_PAD_NATIVE_FUNCTION (*UartGpio) [PCH_SERIAL_IO_PINS_PER_UART_CONTROLLER];

  Status = EFI_SUCCESS;

  ASSERT (SerialIoUartControllerNumber < PCH_SERIALIO_MAX_UART_CONTROLLERS);

  if (GetPchSeries () == PchLp) {
    UartGpio = mPchLpUartGpio;
  } else {
    UartGpio = mPchHUartGpio;
  }

  if (HardwareFlowControl) {
    PinsUsed = PCH_SERIAL_IO_PINS_PER_UART_CONTROLLER;
  } else {
    PinsUsed = PCH_SERIAL_IO_PINS_PER_UART_CONTROLLER_NO_FLOW_CTRL;
  }

  for (Index = 0; Index < PinsUsed; Index++) {
    Status = SetGpioPadMode (UartGpio[SerialIoUartControllerNumber][Index].Pad, UartGpio[SerialIoUartControllerNumber][Index].Mode);
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
    GpioSetInputInversion (UartGpio[SerialIoUartControllerNumber][Index].Pad, 0);
  }
  return Status;
}

/**
  This function sets SerialIo SPI controller pins into native mode

  @param[in]  SerialIoI2CControllerNumber   SPI controller

  @retval Status
**/
EFI_STATUS
GpioSetSerialIoSpiPinsIntoNativeMode (
  IN  UINT32            SerialIoSpiControllerNumber
  )
{
  EFI_STATUS    Status;
  UINTN         Index;
  GPIO_PAD_NATIVE_FUNCTION (*SpiGpio) [PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER];

  Status = EFI_SUCCESS;

  ASSERT (SerialIoSpiControllerNumber < PCH_SERIALIO_MAX_SPI_CONTROLLERS);

  if (GetPchSeries () == PchLp) {
    SpiGpio = mPchLpSpiGpio;
  } else {
    SpiGpio = mPchHSpiGpio;
  }

  for (Index = 0; Index < PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER; Index++) {
    Status = SetGpioPadMode (SpiGpio[SerialIoSpiControllerNumber][Index].Pad, SpiGpio[SerialIoSpiControllerNumber][Index].Mode);
    if (EFI_ERROR (Status)) {
      return EFI_UNSUPPORTED;
    }
    GpioSetInputInversion (SpiGpio[SerialIoSpiControllerNumber][Index].Pad, 0);
  }
  return Status;
}


/**
  This function checks if GPIO pin is a GSPI chip select pin

  @param[in]  GpioPad             GPIO pad
  @param[in]  PadMode             GPIO pad mode

  @retval TRUE                    Pin is in GPIO mode
          FALSE                   Pin is in native mode
**/
BOOLEAN
GpioIsGpioPadAGSpiCsbPin (
  IN  GPIO_PAD        GpioPad,
  IN  GPIO_PAD_MODE   PadMode
  )
{
  UINT32                   ControllerMax;
  UINT32                   ControllerIndex;
  GPIO_PAD_OWN             PadOwnership;
  GPIO_PAD_NATIVE_FUNCTION (*SpiGpio) [PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER];

  if (GetPchSeries () == PchLp) {
    SpiGpio = mPchLpSpiGpio;
    ControllerMax = sizeof (mPchLpSpiGpio) / sizeof (GPIO_PAD_NATIVE_FUNCTION) / PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER;
  } else {
    SpiGpio = mPchHSpiGpio;
    ControllerMax = sizeof (mPchHSpiGpio) / sizeof (GPIO_PAD_NATIVE_FUNCTION) / PCH_SERIAL_IO_PINS_PER_SPI_CONTROLLER;
  }

  for (ControllerIndex = 0; ControllerIndex < ControllerMax; ControllerIndex++) {
    if ((GpioPad == SpiGpio[ControllerIndex][0].Pad) &&
        (PadMode == SpiGpio[ControllerIndex][0].Mode)) {
      GpioGetPadOwnership (SpiGpio[ControllerIndex][0].Pad , &PadOwnership);
      if (PadOwnership == GpioPadOwnHost) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
  }
  return FALSE;
}

/**
  This function checks if GPIO pin for SATA reset port is in GPIO MODE

  @param[in]  SataPort            SATA port number

  @retval TRUE                    Pin is in GPIO mode
          FALSE                   Pin is in native mode
**/
BOOLEAN
GpioIsSataResetPortInGpioMode (
  IN  UINTN           SataPort
  )
{
  EFI_STATUS     Status;
  UINT32         GpioPin;
  GPIO_PAD_MODE  GpioMode;


  if (GetPchSeries () == PchLp) {
    ASSERT (SataPort < PCH_LP_AHCI_MAX_PORTS);
    GpioPin = mPchLpSataPortResetToGpioMap[SataPort].Pad;
  } else {
    ASSERT (SataPort < PCH_H_AHCI_MAX_PORTS);
    GpioPin = mPchHSataPortResetToGpioMap[SataPort].Pad;
  }

  Status =  GetGpioPadMode (GpioPin, &GpioMode);
  if ((EFI_ERROR (Status)) || (GpioMode != GpioPadModeGpio)) {
    return FALSE;
  } else {
    return TRUE;
  }
}


/**
  This function checks if GPIO pin is a SataDevSlp pin

  @param[in]  GpioPad             GPIO pad
  @param[in]  PadMode             GPIO pad mode

  @retval TRUE                    Pin is in GPIO mode
          FALSE                   Pin is in native mode
**/
BOOLEAN
GpioIsPadASataDevSlpPin (
  IN  GPIO_PAD        GpioPad,
  IN  GPIO_PAD_MODE   PadMode
  )
{
  UINT32                    SataDevSlpPinMax;
  UINT32                    SataDevSlpPinIndex;
  GPIO_PAD_OWN              PadOwnership;
  GPIO_PAD_NATIVE_FUNCTION  *SataDevSlpPinToGpioMap;

  if (GetPchSeries () == PchLp) {
    SataDevSlpPinToGpioMap = mPchLpSataDevSlpPinToGpioMap;
    SataDevSlpPinMax = sizeof (mPchLpSataDevSlpPinToGpioMap) /sizeof (GPIO_PAD_NATIVE_FUNCTION);
  } else {
    SataDevSlpPinToGpioMap = mPchHSataDevSlpPinToGpioMap;
    SataDevSlpPinMax = sizeof (mPchHSataDevSlpPinToGpioMap) /sizeof (GPIO_PAD_NATIVE_FUNCTION);
  }

  for (SataDevSlpPinIndex = 0; SataDevSlpPinIndex < SataDevSlpPinMax; SataDevSlpPinIndex++) {
    if ((GpioPad == SataDevSlpPinToGpioMap[SataDevSlpPinIndex].Pad) &&
        (PadMode == SataDevSlpPinToGpioMap[SataDevSlpPinIndex].Mode)) {
      GpioGetPadOwnership (SataDevSlpPinToGpioMap[SataDevSlpPinIndex].Pad , &PadOwnership);
      if (PadOwnership == GpioPadOwnHost) {
        return TRUE;
      } else {
        return FALSE;
      }
    }
  }
  return FALSE;
}

/**
  This function checks if SataDevSlp pin is in native mode

  @param[in]  SataPort            SATA port
  @param[out] DevSlpPad           DevSlpPad

  @retval TRUE                    DevSlp is in native mode
          FALSE                   DevSlp is not in native mode
**/
BOOLEAN
GpioIsSataDevSlpPinEnabled (
  IN  UINTN           SataPort,
  OUT GPIO_PAD        *DevSlpPad
  )
{
  GPIO_PAD_MODE  DevSlpPadMode;
  GPIO_PAD       DevSlpGpioPad;
  GPIO_PAD_MODE  GpioMode;
  EFI_STATUS     Status;

  if (GetPchSeries () == PchLp) {
    ASSERT (SataPort < PCH_LP_AHCI_MAX_PORTS);
    DevSlpGpioPad = mPchLpSataDevSlpPinToGpioMap[SataPort].Pad;
    DevSlpPadMode = mPchLpSataDevSlpPinToGpioMap[SataPort].Mode;
  } else {
    ASSERT (SataPort < PCH_H_AHCI_MAX_PORTS);
    DevSlpGpioPad = mPchHSataDevSlpPinToGpioMap[SataPort].Pad;
    DevSlpPadMode = mPchHSataDevSlpPinToGpioMap[SataPort].Mode;
  }

  Status = GetGpioPadMode (DevSlpGpioPad, &GpioMode);

  if (EFI_ERROR (Status) || (GpioMode != DevSlpPadMode)) {
    *DevSlpPad = 0x0;
    return FALSE;
  } else {
    *DevSlpPad = DevSlpGpioPad;
    return TRUE;
  }
}

