/** @file
 *
 *  Copyright (c) 2018, Andrei Warkentin <andrey.warkentin@gmail.com>
 *
 *  SPDX-License-Identifier: BSD-2-Clause-Patent
 *
 **/

#include <Uefi.h>
#include <Library/HiiLib.h>
#include <Library/DebugLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/DevicePathLib.h>
#include <IndustryStandard/RpiMbox.h>
#include <IndustryStandard/Bcm2836Gpio.h>
#include <Library/GpioLib.h>
#include <Protocol/RpiFirmware.h>
#include "ConfigDxeFormSetGuid.h"

extern UINT8 ConfigDxeHiiBin[];
extern UINT8 ConfigDxeStrings[];

STATIC RASPBERRY_PI_FIRMWARE_PROTOCOL *mFwProtocol;

typedef struct {
  VENDOR_DEVICE_PATH VendorDevicePath;
  EFI_DEVICE_PATH_PROTOCOL End;
} HII_VENDOR_DEVICE_PATH;

STATIC HII_VENDOR_DEVICE_PATH mVendorDevicePath = {
  {
    {
      HARDWARE_DEVICE_PATH,
      HW_VENDOR_DP,
      {
        (UINT8)(sizeof (VENDOR_DEVICE_PATH)),
        (UINT8)((sizeof (VENDOR_DEVICE_PATH)) >> 8)
      }
    },
    CONFIGDXE_FORM_SET_GUID
  },
  {
    END_DEVICE_PATH_TYPE,
    END_ENTIRE_DEVICE_PATH_SUBTYPE,
    {
      (UINT8)(END_DEVICE_PATH_LENGTH),
      (UINT8)((END_DEVICE_PATH_LENGTH) >> 8)
    }
  }
};


STATIC EFI_STATUS
InstallHiiPages (
  VOID
  )
{
  EFI_STATUS     Status;
  EFI_HII_HANDLE HiiHandle;
  EFI_HANDLE     DriverHandle;

  DriverHandle = NULL;
  Status = gBS->InstallMultipleProtocolInterfaces (&DriverHandle,
                  &gEfiDevicePathProtocolGuid,
                  &mVendorDevicePath,
                  NULL);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  HiiHandle = HiiAddPackages (&gConfigDxeFormSetGuid,
                DriverHandle,
                ConfigDxeStrings,
                ConfigDxeHiiBin,
                NULL);

  if (HiiHandle == NULL) {
    gBS->UninstallMultipleProtocolInterfaces (DriverHandle,
           &gEfiDevicePathProtocolGuid,
           &mVendorDevicePath,
           NULL);
    return EFI_OUT_OF_RESOURCES;
  }
  return EFI_SUCCESS;
}


STATIC EFI_STATUS
SetupVariables (
  VOID
  )
{
  UINTN Size;
  UINT32 Var32;
  EFI_STATUS Status;

  /*
   * Create the vars with default value.
   * If we don't, forms won't be able to update.
   */

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"CpuClock",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdCpuClock, PcdGet32 (PcdCpuClock));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable(L"CustomCpuClock",
                            &gConfigDxeFormSetGuid,
                            NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdCustomCpuClock, PcdGet32 (PcdCustomCpuClock));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"SdIsArasan",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdSdIsArasan, PcdGet32 (PcdSdIsArasan));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"MmcDisableMulti",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdMmcDisableMulti, PcdGet32 (PcdMmcDisableMulti));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"MmcForce1Bit",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdMmcForce1Bit, PcdGet32 (PcdMmcForce1Bit));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"MmcForceDefaultSpeed",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdMmcForceDefaultSpeed, PcdGet32 (PcdMmcForceDefaultSpeed));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"MmcSdDefaultSpeedMHz",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdMmcSdDefaultSpeedMHz, PcdGet32 (PcdMmcSdDefaultSpeedMHz));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"MmcSdHighSpeedMHz",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdMmcSdHighSpeedMHz, PcdGet32 (PcdMmcSdHighSpeedMHz));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"DebugEnableJTAG",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdDebugEnableJTAG, PcdGet32 (PcdDebugEnableJTAG));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"DebugShowUEFIExit",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdDebugShowUEFIExit, PcdGet32 (PcdDebugShowUEFIExit));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"DisplayEnableVModes",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdDisplayEnableVModes, PcdGet32 (PcdDisplayEnableVModes));
  }

  Size = sizeof (UINT32);
  Status = gRT->GetVariable (L"DisplayEnableSShot",
                  &gConfigDxeFormSetGuid,
                  NULL, &Size, &Var32);
  if (EFI_ERROR (Status)) {
    PcdSet32 (PcdDisplayEnableSShot, PcdGet32 (PcdDisplayEnableSShot));
  }

  return EFI_SUCCESS;
}


STATIC VOID
ApplyVariables (
  VOID
  )
{
  UINTN Gpio34Group;
  UINTN Gpio48Group;
  EFI_STATUS Status;
  UINT32 CpuClock = PcdGet32 (PcdCpuClock);
  UINT32 CustomCpuClock = PcdGet32 (PcdCustomCpuClock);
  UINT32 Rate = 0;

  if (CpuClock != 0) {
    if (CpuClock == 2) {
      /*
       * Maximum: 1.2GHz on RPi 3, 1.4GHz on RPi 3B+, unless
       * overridden with arm_freq=xxx in config.txt.
       */
      Status = mFwProtocol->GetMaxClockRate (RPI_MBOX_CLOCK_RATE_ARM, &Rate);
      if (Status != EFI_SUCCESS) {
        DEBUG ((DEBUG_ERROR, "Couldn't get the max CPU speed, leaving as is: %r\n", Status));
      }
    } else if (CpuClock == 3) {
      Rate = CustomCpuClock * 1000000;
    } else {
      Rate = 600 * 1000000;
    }
  }

  if (Rate != 0) {
    DEBUG ((DEBUG_INFO, "Setting CPU speed to %uHz\n", Rate));
    Status = mFwProtocol->SetClockRate (RPI_MBOX_CLOCK_RATE_ARM, Rate, 1);
    if (Status != EFI_SUCCESS) {
      DEBUG ((DEBUG_ERROR, "Couldn't set the CPU speed: %r\n", Status));
    }
  }

  Status = mFwProtocol->GetClockRate (RPI_MBOX_CLOCK_RATE_ARM, &Rate);
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "Couldn't get the CPU speed: %r\n", Status));
  } else {
    DEBUG ((DEBUG_INFO, "Current CPU speed is %uHz\n", Rate));
  }

  /*
   * Switching two groups around, so disable both first.
   *
   * No, I've not seen a problem, but having a group be
   * routed to two sets of pins seems like asking for trouble.
   */
  GpioPinFuncSet (34, GPIO_FSEL_INPUT);
  GpioPinFuncSet (35, GPIO_FSEL_INPUT);
  GpioPinFuncSet (36, GPIO_FSEL_INPUT);
  GpioPinFuncSet (37, GPIO_FSEL_INPUT);
  GpioPinFuncSet (38, GPIO_FSEL_INPUT);
  GpioPinFuncSet (39, GPIO_FSEL_INPUT);
  GpioPinFuncSet (48, GPIO_FSEL_INPUT);
  GpioPinFuncSet (49, GPIO_FSEL_INPUT);
  GpioPinFuncSet (50, GPIO_FSEL_INPUT);
  GpioPinFuncSet (51, GPIO_FSEL_INPUT);
  GpioPinFuncSet (52, GPIO_FSEL_INPUT);
  GpioPinFuncSet (53, GPIO_FSEL_INPUT);
  if (PcdGet32 (PcdSdIsArasan)) {
    DEBUG ((DEBUG_INFO, "Routing SD to Arasan\n"));
    Gpio48Group = GPIO_FSEL_ALT3;
    /*
     * Route SDIO to SdHost.
     */
    Gpio34Group = GPIO_FSEL_ALT0;
  } else {
    DEBUG ((DEBUG_INFO, "Routing SD to SdHost\n"));
    Gpio48Group = GPIO_FSEL_ALT0;
    /*
     * Route SDIO to Arasan.
     */
    Gpio34Group = GPIO_FSEL_ALT3;
  }
  GpioPinFuncSet (34, Gpio34Group);
  GpioPinFuncSet (35, Gpio34Group);
  GpioPinFuncSet (36, Gpio34Group);
  GpioPinFuncSet (37, Gpio34Group);
  GpioPinFuncSet (38, Gpio34Group);
  GpioPinFuncSet (39, Gpio34Group);
  GpioPinFuncSet (48, Gpio48Group);
  GpioPinFuncSet (49, Gpio48Group);
  GpioPinFuncSet (50, Gpio48Group);
  GpioPinFuncSet (51, Gpio48Group);
  GpioPinFuncSet (52, Gpio48Group);
  GpioPinFuncSet (53, Gpio48Group);

  /*
   * JTAG pin    JTAG sig    GPIO      Mode    Header pin
   * 1           VREF        N/A               1
   * 3           nTRST       GPIO22    ALT4    15
   * 4           GND         N/A               9
   * 5           TDI         GPIO4     ALT5    7
   * 7           TMS         GPIO27    ALT4    13
   * 9           TCK         GPIO25    ALT4    22
   * 11          RTCK        GPIO23    ALT4    16
   * 13          TDO         GPIO24    ALT4    18
   */
  if (PcdGet32 (PcdDebugEnableJTAG)) {
    GpioPinFuncSet (22, GPIO_FSEL_ALT4);
    GpioPinFuncSet (4, GPIO_FSEL_ALT5);
    GpioPinFuncSet (27, GPIO_FSEL_ALT4);
    GpioPinFuncSet (25, GPIO_FSEL_ALT4);
    GpioPinFuncSet (23, GPIO_FSEL_ALT4);
    GpioPinFuncSet (24, GPIO_FSEL_ALT4);
  } else {
    GpioPinFuncSet (22, GPIO_FSEL_INPUT);
    GpioPinFuncSet (4, GPIO_FSEL_INPUT);
    GpioPinFuncSet (27, GPIO_FSEL_INPUT);
    GpioPinFuncSet (25, GPIO_FSEL_INPUT);
    GpioPinFuncSet (23, GPIO_FSEL_INPUT);
    GpioPinFuncSet (24, GPIO_FSEL_INPUT);
  }
}


EFI_STATUS
EFIAPI
ConfigInitialize (
  IN EFI_HANDLE ImageHandle,
  IN EFI_SYSTEM_TABLE *SystemTable
  )
{
  EFI_STATUS Status;

  Status = gBS->LocateProtocol (&gRaspberryPiFirmwareProtocolGuid,
                  NULL, (VOID**)&mFwProtocol);
  ASSERT_EFI_ERROR (Status);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = SetupVariables ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "Couldn't not setup NV vars: %r\n", Status));
  }

  ApplyVariables ();
  Status = gBS->InstallProtocolInterface (&ImageHandle,
                  &gRaspberryPiConfigAppliedProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  NULL);
  ASSERT_EFI_ERROR (Status);

  Status = InstallHiiPages ();
  if (Status != EFI_SUCCESS) {
    DEBUG ((DEBUG_ERROR, "Couldn't install ConfigDxe configuration pages: %r\n", Status));
  }

  return EFI_SUCCESS;
}
