/** @file
  Platform Clocks Lib file

  @copyright
  Copyright 1999 - 2021 Intel Corporation. <BR>

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

#include <Base.h>
#include <Library/IoLib.h>
#include <Library/PlatformClocksLib.h>
#include <Ppi/Smbus2.h>
#include <Ppi/SmbusPolicy.h>
#include <Library/DebugLib.h>
#include <Ppi/Stall.h>
#include <Library/HobLib.h>
#include <Platform.h>
#include <Library/BaseMemoryLib.h>
#include <Library/PeiServicesLib.h>

#define MAX_SMBUS_RETRIES        10


EFI_STATUS
ConfigureClockGeneratorOnSecondarySmbus (
  IN    EFI_PEI_SERVICES          **PeiServices,
  IN    EFI_PEI_SMBUS2_PPI        *SmbusPpi,
  IN    UINT8                     ClockAddress,
  IN    UINTN                     ConfigurationTableLength,
  IN    BOOLEAN                   EnableSpreadSpectrum,
  IN    CLOCK_GENERATOR_DETAILS   *mSupportedClockGeneratorT,
  IN OUT UINT8                    *ConfigurationTable
  );

/**

  Configure the clock generator using the SMBUS PPI services.

  This function performs a block write, and dumps debug information.

  @param PeiServices               - General purpose services available to every PEIM.
  @param ClockType                 - Clock generator's model name.
  @param ClockAddress              - SMBUS address of clock generator.
  @param ConfigurationTableLength  - Length of configuration table.
  @param ConfigurationTable        - Pointer of configuration table.

  @retval EFI_SUCCESS - Operation success.

**/
EFI_STATUS
ConfigureClockGenerator (
  IN EFI_PEI_SERVICES             **PeiServices,
  IN     CLOCK_GENERATOR_TYPE     ClockType,
  IN     UINT8                    ClockAddress,
  IN     UINTN                    ConfigurationTableLength,
  IN OUT UINT8                    *ConfigurationTable,
  IN     BOOLEAN                  EnableSpreadSpectrum,
  IN     CLOCK_GENERATOR_DETAILS  *mSupportedClockGeneratorT,
  IN     UINT8                    SecondarySmbus
  )
{
  EFI_STATUS                      Status;
  EFI_SMBUS_DEVICE_ADDRESS        SlaveAddress;
  UINT8                           Buffer[MAX_CLOCK_GENERATOR_BUFFER_LENGTH];
  UINTN                           Length;
  EFI_SMBUS_DEVICE_COMMAND        Command;
  EFI_PEI_SMBUS2_PPI              *SmbusPpi;
  UINT8                           SmbErrorsCounter;

  //
  // Locate SmBus Ppi
  //
  Status = PeiServicesLocatePpi (&gEfiPeiSmbus2PpiGuid, 0, NULL, (VOID** ) &SmbusPpi);
  ASSERT_EFI_ERROR (Status);

  //
  // Verify input arguments
  //
  ASSERT (ConfigurationTableLength >= 8);
  ASSERT (ConfigurationTableLength <= MAX_CLOCK_GENERATOR_BUFFER_LENGTH);
  ASSERT (ClockType < ClockGeneratorMax);
  ASSERT (ConfigurationTable != NULL);

  //
  // Init some local vars
  //
  SlaveAddress.SmbusDeviceAddress = ClockAddress >> 1;
  Length = sizeof (Buffer);
  Command = 0;

  if (SecondarySmbus == TRUE) {

    return (ConfigureClockGeneratorOnSecondarySmbus(PeiServices,
                                          SmbusPpi,
                                          ClockAddress,
                                          ConfigurationTableLength,
                                          EnableSpreadSpectrum,
                                          mSupportedClockGeneratorT,
                                          ConfigurationTable
                                         ));
  } else {
    //
    // Not LightningRidge
    // Read the clock generator on the primary SMBus
    //

    SmbErrorsCounter = 0;

    do
    {
    Status = SmbusPpi->Execute (
      SmbusPpi,
      SlaveAddress,
      Command,
      EfiSmbusReadBlock,
      FALSE,
      &Length,
      Buffer
      );
      if(Status != EFI_SUCCESS)
      {
        DEBUG ((EFI_D_ERROR, "SMBUS reading error\n"));

      }

      SmbErrorsCounter ++;
    }
    while ((Status != EFI_SUCCESS) && (SmbErrorsCounter < MAX_SMBUS_RETRIES));

    //
    // Sanity check that the requested clock type is present in our supported clocks table
    //
    DEBUG ((DEBUG_INFO, "Expected Clock Generator ID is %x, populated %x\n", mSupportedClockGeneratorT->ClockId, Buffer[7]));

    if (EnableSpreadSpectrum) {
      Buffer[mSupportedClockGeneratorT->SpreadSpectrumByteOffset] |= mSupportedClockGeneratorT->SpreadSpectrumBitOffset;

      if (ClockType == ClockGeneratorCk420) {

        // Ensure that the clock chip is operating in normal mode.
        //
        Buffer[10] &= ~BIT7;
      }
    } else {
      Buffer[mSupportedClockGeneratorT->SpreadSpectrumByteOffset] &= ~(mSupportedClockGeneratorT->SpreadSpectrumBitOffset);
    }

#ifdef EFI_DEBUG
    {
      UINT8   i;
      DEBUG ((DEBUG_INFO, "SlaveAddress.SmbusDeviceAddress %x: Size =%x\n", SlaveAddress.SmbusDeviceAddress, Length));
      for (i = 0; i < ConfigurationTableLength; i++) {
        DEBUG ((DEBUG_INFO, "Default Clock Generator Byte %d: %x\n", i, Buffer[i]));
      }
    }
#endif

    //
    // Program clock generator, using the platform default values
    //

  SmbErrorsCounter = 0;
  do
  {
    Command = 0;
    Status = SmbusPpi->Execute (
      SmbusPpi,
      SlaveAddress,
      Command,
      EfiSmbusWriteBlock,
      FALSE,
      &Length,  // &ConfigurationTableLength,
      Buffer   //ConfigurationTable
      );
   // ASSERT_EFI_ERROR (Status);


     if(Status != EFI_SUCCESS)
     {
       DEBUG ((EFI_D_ERROR, "SMBUS writing error\n"));
     }

     SmbErrorsCounter ++;
   }
   while ((Status != EFI_SUCCESS) && (SmbErrorsCounter < MAX_SMBUS_RETRIES));

    //
    // Dump contents after write
    //
#ifdef EFI_DEBUG
    {
      UINT8   i;
    SlaveAddress.SmbusDeviceAddress = ClockAddress >> 1;
    Length = sizeof (Buffer);
    Command = 0;
    Status =  SmbusPpi->Execute (
        SmbusPpi,
        SlaveAddress,
        Command,
        EfiSmbusReadBlock,
        FALSE,
        &Length,
        Buffer
        );

      for (i = 0; i < ConfigurationTableLength; i++) {
        DEBUG ((DEBUG_INFO, "Clock Generator Byte %d: %x\n", i, Buffer[i]));
      }
    }
#endif

    return EFI_SUCCESS;
  } // else (<not Kahuna>)
}


/**

  Configure clock generator on Kahuna
  Clock gen is on a secondary SMBus

  @param IN EFI_PEI_SERVICES         **PeiServices - Pointer to PEI Services table
  @param IN EFI_PEI_SMBUS2_PPI       *SmbusPpi - Pointer to SMBUs services PPI
  @param IN UINT8                    ClockAddress - SMBus address of clock gen
  @param IN UINT8                    *Buffer - Pointer to buffer containing byte stream to send to clock gen
  @param IN UINTN                    Length - Number of bytes in buffer

  @retval EFI_SUCCESS    The function completed successfully.
  @retval EFI_INVALID_PARAMETER     The function cannot continue due to length is out of bound.

**/
EFI_STATUS
ConfigureClockGeneratorOnSecondarySmbus (
  IN EFI_PEI_SERVICES         **PeiServices,
  IN EFI_PEI_SMBUS2_PPI       *SmbusPpi,
  IN UINT8                    ClockAddress,
  IN UINTN                    ConfigurationTableLength,
  IN BOOLEAN                  EnableSpreadSpectrum,
  IN CLOCK_GENERATOR_DETAILS  *mSupportedClockGeneratorT,
  IN OUT UINT8                *ConfigurationTable
  )
{
  EFI_PEI_STALL_PPI           *StallPpi;
  EFI_STATUS                  Status;
  EFI_SMBUS_DEVICE_ADDRESS    SlaveAddress;
  EFI_SMBUS_DEVICE_COMMAND    SmbusCommand;
  UINTN                       SmbusLength;
  UINT8                       SmbusData[MAX_CLOCK_GENERATOR_BUFFER_LENGTH];
  UINT8                       Buffer[MAX_CLOCK_GENERATOR_BUFFER_LENGTH];
  UINT8                       Length;

  ZeroMem (Buffer, sizeof(Buffer));

  if (ConfigurationTableLength > MAX_CLOCK_GENERATOR_BUFFER_LENGTH) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Locate Stall PPI
  //
  Status = PeiServicesLocatePpi (&gEfiPeiStallPpiGuid, 0, NULL, (VOID** ) &StallPpi);
  ASSERT_EFI_ERROR (Status);

  //
  // Get length of payload to send to clock gen
  //
  Length = (UINT8) ConfigurationTableLength;

  //
  // Copy the default clock generator data into Buffer
  //
  CopyMem ((VOID*)Buffer, ConfigurationTable, Length);

  //
  // Set spread spectrum bit in Buffer or clear it?
  //
  if (EnableSpreadSpectrum) {
    Buffer[mSupportedClockGeneratorT->SpreadSpectrumByteOffset] |= mSupportedClockGeneratorT->SpreadSpectrumBitOffset;

    if (mSupportedClockGeneratorT->ClockType == ClockGeneratorCk420) {

      //
      // Ensure that the clock chip is operating in normal mode.
      //
      Buffer[10] &= ~BIT7;
    }
  } else {
    Buffer[mSupportedClockGeneratorT->SpreadSpectrumByteOffset] &= ~(mSupportedClockGeneratorT->SpreadSpectrumBitOffset);
  }

  //
  // Now encapsulate the data for a Write Block command to the clock gen
  //  as the payload in a Write Block command to the SMBus bridge
  //
  // Segment address = 0xF2, this becomes slave address
  // Slave address (clock gen) = ClockAddress, this becomes slave command
  //
  SlaveAddress.SmbusDeviceAddress = (0xF2 >> 1);
  SmbusCommand = ClockAddress;

  //
  // Set byte index in clock gen to start with, always 0
  //
  SmbusData[0] = 0;

  //
  // Set byte count clock gen wants to see
  //
  SmbusData[1] = (UINT8) Length;

  //
  // Payload byte count for SMBus bridge
  //
  SmbusLength = Length + 2;
  if (SmbusLength > MAX_CLOCK_GENERATOR_BUFFER_LENGTH) {
    return EFI_INVALID_PARAMETER;
  }

  //
  // Copy the clock gen data to the SMBus buffer
  //
  CopyMem ((VOID *)(((UINT8*)SmbusData) + 2), (VOID *)Buffer, Length);

  //
  // Use EfiSmbusWriteBlock to communicate with clock gen
  //
  Status = SmbusPpi->Execute( SmbusPpi,
                              SlaveAddress,
                              SmbusCommand,
                              EfiSmbusWriteBlock,
                              FALSE,
                              &SmbusLength,
                              &SmbusData );
  return Status;
}
