/** @file
  Gpio setting for multiplatform..

  Copyright (c) 2013 - 2019, Intel Corporation. All rights reserved.<BR>

  SPDX-License-Identifier: BSD-2-Clause-Patent

**/

#include <BoardGpios.h>
#include <Guid/SetupVariable.h>

//
//AlpineValley platform ocde begin
//
#define AV_SC_REG_GPIOS_MUXES_SEL0 0x48
#define AV_SC_REG_GPIOS_MUXES_SEL1 0x4C
#define AV_SC_REG_GPIOS_MUXES_SEL2 0x50
#define AV_SC_REG_GPIOS_MUXES_EN0  0x54
#define AV_SC_REG_GPIOS_MUXES_EN1  0x58
#define AV_SC_REG_GPIOS_MUXES_EN2  0x5C
//
//AlpineValley platform code end
//

/**
  @param None

  @retval EFI_SUCCESS    The function completed successfully.

**/
EFI_STATUS
ConfigurePlatformSysCtrlGpio (
  IN EFI_PEI_SERVICES                   **PeiServices,
  IN EFI_PEI_NOTIFY_DESCRIPTOR          *NotifyDescriptor,
  IN VOID                               *SmbusPpi
  )
{
  //
  //AlpineValley platform code begin
  //
  // Initialize GPIO Settings:
  //
  UINT32        Status;
  EFI_PLATFORM_INFO_HOB               *PlatformInfoHob;

   DEBUG ((EFI_D_INFO, "ConfigurePlatformSysCtrlGpio()...\n"));

  //
  // Obtain Platform Info from HOB.
  //
  Status = GetPlatformInfoHob ((const EFI_PEI_SERVICES **)PeiServices, &PlatformInfoHob);
  ASSERT_EFI_ERROR (Status);

  //
  // The GPIO settings are dependent upon the platform.  Obtain the Board ID through
  // the EC to determine the current platform.
  //
   DEBUG ((EFI_D_INFO, "Platform Flavor | Board ID = 0x%X | 0x%X\n", PlatformInfoHob->PlatformFlavor, PlatformInfoHob->BoardId));



  Status = (**PeiServices).LocatePpi (
                            (const EFI_PEI_SERVICES **)PeiServices,
                            &gEfiPeiSmbus2PpiGuid,
                            0,
                            NULL,
                            (void **)&SmbusPpi
                            );
  ASSERT_EFI_ERROR (Status);

  //
  // Select/modify the GPIO initialization data based on the Board ID.
  //
  switch (PlatformInfoHob->BoardId)
  {
    default:
      Status = EFI_SUCCESS;

      //
      // Do nothing for other RVP boards.
      //
      break;
  }
  return Status;
}

static EFI_PEI_NOTIFY_DESCRIPTOR    mNotifyList[] = {
  {
    EFI_PEI_PPI_DESCRIPTOR_NOTIFY_CALLBACK| EFI_PEI_PPI_DESCRIPTOR_TERMINATE_LIST,
    &gEfiPeiSmbus2PpiGuid,
    ConfigurePlatformSysCtrlGpio
  }
};

EFI_STATUS
InstallPlatformSysCtrlGPIONotify (
  IN CONST EFI_PEI_SERVICES           **PeiServices
  )
{
  EFI_STATUS                    Status;

  DEBUG ((EFI_D_INFO, "InstallPlatformSysCtrlGPIONotify()...\n"));

  Status = (*PeiServices)->NotifyPpi(PeiServices, &mNotifyList[0]);
  ASSERT_EFI_ERROR (Status);
  return EFI_SUCCESS;

}

#define V_PCH_ILB_IRQE_UARTIRQEN_IRQ3             BIT3 // UART IRQ3 Enable

/**
  Returns the Correct GPIO table for Mobile/Desktop respectively.
  Before call it, make sure PlatformInfoHob->BoardId&PlatformFlavor is get correctly.

  @param PeiServices             General purpose services available to every PEIM.
  @param PlatformInfoHob         PlatformInfoHob pointer with PlatformFlavor specified.
  @param BoardId                 BoardId ID as determined through the EC.

  @retval EFI_SUCCESS            The function completed successfully.
  @retval EFI_DEVICE_ERROR       KSC fails to respond.

**/
EFI_STATUS
MultiPlatformGpioTableInit (
  IN CONST EFI_PEI_SERVICES     **PeiServices,
  IN EFI_PLATFORM_INFO_HOB      *PlatformInfoHob
  )
{
  EFI_STATUS                      Status;
  EFI_PEI_READ_ONLY_VARIABLE2_PPI *PeiReadOnlyVarPpi;
  UINTN                           VarSize;
  SYSTEM_CONFIGURATION            SystemConfiguration;

  DEBUG ((EFI_D_INFO, "MultiPlatformGpioTableInit()...\n"));

  //
  // Select/modify the GPIO initialization data based on the Board ID.
  //
  switch (PlatformInfoHob->BoardId) {

  case BOARD_ID_MINNOW2: // Minnow2
  case BOARD_ID_MINNOW2_TURBOT: 
   Status = (**PeiServices).LocatePpi (
                             PeiServices,
                             &gEfiPeiReadOnlyVariable2PpiGuid,
                             0,
                             NULL,
                             (void **)&PeiReadOnlyVarPpi
                             );
    ASSERT_EFI_ERROR (Status);
   
    VarSize = sizeof (SYSTEM_CONFIGURATION);
    Status = PeiReadOnlyVarPpi->GetVariable ( 
                                  PeiReadOnlyVarPpi, 
                                  PLATFORM_SETUP_VARIABLE_NAME, 
                                  &gEfiSetupVariableGuid,
                                  NULL,
                                  &VarSize,
                                  &SystemConfiguration
                                  );
                                                                       
     if (SystemConfiguration.GpioWakeCapability == 1) {
      PlatformInfoHob->PlatformCfioData     = (EFI_PHYSICAL_ADDRESS)(UINTN) &mMinnow2CfioInitData2;
     }
     else {
      PlatformInfoHob->PlatformCfioData     = (EFI_PHYSICAL_ADDRESS)(UINTN) &mMinnow2CfioInitData;
     }	
	 
     PlatformInfoHob->PlatformGpioData_NC  = (EFI_PHYSICAL_ADDRESS)(UINTN) &mMinnow2_GpioInitData_NC[0];
     PlatformInfoHob->PlatformGpioData_SC  = (EFI_PHYSICAL_ADDRESS)(UINTN) &mMinnow2_GpioInitData_SC[0];
     PlatformInfoHob->PlatformGpioData_SUS = (EFI_PHYSICAL_ADDRESS)(UINTN) &mMinnow2_GpioInitData_SUS[0];
     break;

  }

  return EFI_SUCCESS;
}

UINT32
GPIORead32 (
  IN  UINT32 mmio_conf
  )
{
  UINT32 conf_val;
  UINT32 i;
  conf_val = MmioRead32(mmio_conf);
  for(i=0;i<5;i++){
    if(conf_val == 0xffffffff)
      conf_val = MmioRead32(mmio_conf);
      else
        break;
  }

  return conf_val;
}

/**

  Set GPIO CONF0 and PAD_VAL registers for NC/SC/SUS GPIO clusters

  @param Gpio_Mmio_Offset          GPIO_SCORE_OFFSET or GPIO_NCORE_OFFSET or GPIO_SSUS_OFFSET.
  @param Gpio_Pin_Num              Pin numbers to config for each GPIO clusters.
  @param Gpio_Conf_Data            GPIO_CONF_PAD_INIT data array for each GPIO clusters.

**/
VOID
InternalGpioConfig (
  IN UINT32             Gpio_Mmio_Offset,
  IN UINT32             Gpio_Pin_Num,
  GPIO_CONF_PAD_INIT*   Gpio_Conf_Data
  )
{
  UINT32    index;
  UINT32    mmio_conf0;
  UINT32    mmio_padval;
  PAD_CONF0 conf0_val;
  PAD_VAL   pad_val;

  //
  // GPIO WELL -- Memory base registers
  //

  // A0 BIOS Spec doesn't mention it although X0 does. comment out now.
  // GPIO write 0x01001002 to IOBASE + Gpio_Mmio_Offset + 0x0900
  //
  for(index=0; index < Gpio_Pin_Num; index++)
  {
  	//
    // Calculate the MMIO Address for specific GPIO pin CONF0 register pointed by index.
    //
    mmio_conf0 = IO_BASE_ADDRESS + Gpio_Mmio_Offset + R_PCH_CFIO_PAD_CONF0 + Gpio_Conf_Data[index].offset * 16;
    mmio_padval= IO_BASE_ADDRESS + Gpio_Mmio_Offset + R_PCH_CFIO_PAD_VAL   + Gpio_Conf_Data[index].offset * 16;

#ifdef EFI_DEBUG
    DEBUG ((EFI_D_INFO, "%s, ", Gpio_Conf_Data[index].pad_name));

#endif
    DEBUG ((EFI_D_INFO, "Usage = %d, Func# = %d, IntType = %d, Pull Up/Down = %d, MMIO Base = 0x%08x, ",
      Gpio_Conf_Data[index].usage,
      Gpio_Conf_Data[index].func,
      Gpio_Conf_Data[index].int_type,
      Gpio_Conf_Data[index].pull,
      mmio_conf0));

    //
    // Step 1: PadVal Programming.
    //
    pad_val.dw = GPIORead32(mmio_padval);

    //
    // Config PAD_VAL only for GPIO (Non-Native) Pin
    //
    if(Native != Gpio_Conf_Data[index].usage)
    {
      pad_val.dw &= ~0x6; // Clear bits 1:2
      pad_val.dw |= (Gpio_Conf_Data[index].usage & 0x6);  // Set bits 1:2 according to PadVal

        //
        // set GPO default value
        //
        if(Gpio_Conf_Data[index].usage == GPO && Gpio_Conf_Data[index].gpod4 != NA)
        {
        pad_val.r.pad_val = Gpio_Conf_Data[index].gpod4;
        }
    }


    DEBUG ((EFI_D_INFO, "Set PAD_VAL = 0x%08x, ", pad_val.dw));

    MmioWrite32(mmio_padval, pad_val.dw);

    //
    // Step 2: CONF0 Programming
    // Read GPIO default CONF0 value, which is assumed to be default value after reset.
    //
    conf0_val.dw = GPIORead32(mmio_conf0);

    //
    // Set Function #
    //
    conf0_val.r.Func_Pin_Mux = Gpio_Conf_Data[index].func;

    if(GPO == Gpio_Conf_Data[index].usage)
    {
      //
      // If used as GPO, then internal pull need to be disabled.
      //
      conf0_val.r.Pull_assign = 0;  // Non-pull
    }
    else
    {
      //
      // Set PullUp / PullDown
      //
      if(P_20K_H == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x1;  // PullUp
        conf0_val.r.Pull_strength = 0x2;// 20K
      }
      else if(P_20K_L == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x2;  // PullDown
        conf0_val.r.Pull_strength = 0x2;// 20K
      }
      else if(P_10K_H == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x1; 	// PullUp
        conf0_val.r.Pull_strength = 0x1;// 10K
      }
      else if(P_10K_L == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x2; 	// PullDown
        conf0_val.r.Pull_strength = 0x1;// 10K
      }
      else if(P_2K_H == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x1;  // PullUp
        conf0_val.r.Pull_strength = 0x0;// 2K
      }
      else if(P_2K_L == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0x2;  // PullDown
        conf0_val.r.Pull_strength = 0x0;// 2K
      }
      else if(P_NONE == Gpio_Conf_Data[index].pull)
      {
        conf0_val.r.Pull_assign = 0;	// Non-pull
      }
      else
      {
        ASSERT(FALSE);  // Invalid value
      }
    }


    //
    // Set INT Trigger Type
    //
    conf0_val.dw &= ~0x0f000000;  // Clear bits 27:24

    //
    // Set INT Trigger Type
    //
    if(TRIG_ == Gpio_Conf_Data[index].int_type)
    {
      //
      // Interrupt not capable, clear bits 27:24
      //
    }
    else
    {
      conf0_val.dw |= (Gpio_Conf_Data[index].int_type & 0x0f)<<24;
    }

    DEBUG ((EFI_D_INFO, "Set CONF0 = 0x%08x\n", conf0_val.dw));

    //
    // Write back the targeted GPIO config value according to platform (board) GPIO setting.
    //
    MmioWrite32 (mmio_conf0, conf0_val.dw);
  }

  //
  // A0 BIOS Spec doesn't mention it although X0 does. comment out now.
  // GPIO SCORE write 0x01001002 to IOBASE + 0x0900
  //
}

/**
  Returns the Correct GPIO table for Mobile/Desktop respectively.
  Before call it, make sure PlatformInfoHob->BoardId&PlatformFlavor is get correctly.

  @param PeiServices               General purpose services available to every PEIM.
  @param PlatformInfoHob           PlatformInfoHob pointer with PlatformFlavor specified.
  @param BoardId                   BoardId ID as determined through the EC.

  @retval EFI_SUCCESS              The function completed successfully.
  @retval EFI_DEVICE_ERROR         KSC fails to respond.

**/
EFI_STATUS
MultiPlatformGpioProgram (
  IN CONST EFI_PEI_SERVICES     **PeiServices,
  IN EFI_PLATFORM_INFO_HOB      *PlatformInfoHob
  )
{
#if !_SIMIC_
  CFIO_INIT_STRUCT*           PlatformCfioDataPtr;

  PlatformCfioDataPtr = (CFIO_INIT_STRUCT *) (UINTN) PlatformInfoHob->PlatformCfioData;
  DEBUG ((EFI_D_INFO, "MultiPlatformGpioProgram()...\n"));

  //
  //  SCORE GPIO WELL -- IO base registers
  //

  //
  // GPIO_USE_SEL Register -> 1 = GPIO 0 = Native
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_USE_SEL, PlatformCfioDataPtr->Use_Sel_SC0);

  //
  // Set GP_LVL Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_LVL , PlatformCfioDataPtr->GP_Lvl_SC0);

  //
  // GP_IO_SEL Register -> 1 = Input 0 = Output.  If Native Mode don't care
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_IO_SEL, PlatformCfioDataPtr->Io_Sel_SC0);

  //
  // GPIO Triger Positive Edge Enable Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_TPE, PlatformCfioDataPtr->TPE_SC0);

  //
  //  GPIO Trigger Negative Edge Enable Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_TNE, PlatformCfioDataPtr->TNE_SC0);

  //
  //  GPIO Trigger Status
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_TS, PlatformCfioDataPtr->TS_SC0);

  //
  // GPIO_USE_SEL2 Register -> 1 = GPIO 0 = Native
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_USE_SEL2, PlatformCfioDataPtr->Use_Sel_SC1);

  //
  // Set GP_LVL2 Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_LVL2, PlatformCfioDataPtr->GP_Lvl_SC1);

  //
  // GP_IO_SEL2 Register -> 1 = Input 0 = Output.  If Native Mode don't care
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_IO_SEL2, PlatformCfioDataPtr->Io_Sel_SC1);

  //
  // GPIO_USE_SEL3 Register -> 1 = GPIO 0 = Native
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_USE_SEL3, PlatformCfioDataPtr->Use_Sel_SC2);

  //
  // Set GP_LVL3 Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_LVL3, PlatformCfioDataPtr->GP_Lvl_SC2);

  //
  // GP_IO_SEL3 Register -> 1 = Input 0 = Output if Native Mode don't care
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SC_IO_SEL3, PlatformCfioDataPtr->Io_Sel_SC2);

  //
  //  SUS GPIO WELL -- IO base registers
  //

  //
  // GPIO_USE_SEL Register -> 1 = GPIO 0 = Native
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_USE_SEL, PlatformCfioDataPtr->Use_Sel_SS);

  //
  // Set GP_LVL Register
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_LVL , PlatformCfioDataPtr->GP_Lvl_SS);

  //
  // GP_IO_SEL Register -> 1 = Input 0 = Output.  If Native Mode don't care.
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_IO_SEL, PlatformCfioDataPtr->Io_Sel_SS);

  //
  // GPIO Triger Positive Edge Enable Register.
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_TPE, PlatformCfioDataPtr->TPE_SS);

  //
  //  GPIO Trigger Negative Edge Enable Register.
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_TNE, PlatformCfioDataPtr->TNE_SS);

  //
  //  GPIO Trigger Status.
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_TS, PlatformCfioDataPtr->TS_SS);

  //
  //  GPIO Wake Enable.
  //
  IoWrite32 (GPIO_BASE_ADDRESS + R_PCH_GPIO_SUS_WAKE_EN, PlatformCfioDataPtr->WE_SS);

  //
  // Config SC/NC/SUS GPIO Pins
  //
  switch (PlatformInfoHob->BoardId) {
    case BOARD_ID_MINNOW2:
    case BOARD_ID_MINNOW2_TURBOT:
      DEBUG ((EFI_D_INFO, "Start to config Minnow2 GPIO pins\n"));
      InternalGpioConfig(GPIO_SCORE_OFFSET, sizeof(mMinnow2_GpioInitData_SC)/sizeof(mMinnow2_GpioInitData_SC[0]),   (GPIO_CONF_PAD_INIT *) (UINTN) PlatformInfoHob->PlatformGpioData_SC);
      InternalGpioConfig(GPIO_NCORE_OFFSET, sizeof(mMinnow2_GpioInitData_NC)/sizeof(mMinnow2_GpioInitData_NC[0]),   (GPIO_CONF_PAD_INIT *) (UINTN) PlatformInfoHob->PlatformGpioData_NC);
      InternalGpioConfig(GPIO_SSUS_OFFSET,  sizeof(mMinnow2_GpioInitData_SUS)/sizeof(mMinnow2_GpioInitData_SUS[0]), (GPIO_CONF_PAD_INIT *) (UINTN) PlatformInfoHob->PlatformGpioData_SUS);
      break;
    default:

      break;
  }

   //
   // configure the CFIO Pnp settings
   //
   if (PlatformInfoHob->CfioEnabled) {
     if (PlatformInfoHob->BoardId == BOARD_ID_MINNOW2 || PlatformInfoHob->BoardId == BOARD_ID_MINNOW2_TURBOT){
       InternalGpioConfig(GPIO_SCORE_OFFSET, sizeof(mNB_BB_FAB3_GpioInitData_SC_TRI)/sizeof(mNB_BB_FAB3_GpioInitData_SC_TRI[0]), (GPIO_CONF_PAD_INIT *) (UINTN)PlatformInfoHob->PlatformGpioData_SC_TRI);
     }
   }
#else
   DEBUG ((EFI_D_INFO, "Skip MultiPlatformGpioProgram()...for SIMICS or HYB model\n"));
#endif
  return EFI_SUCCESS;
}

