/** @file
*
*  Copyright (c) 2018, Linaro Ltd. All rights reserved.
*
*  SPDX-License-Identifier: BSD-2-Clause-Patent
*
**/

#include <Guid/EventGroup.h>

#include <Hi3660.h>

#include <Library/BaseLib.h>
#include <Library/BaseMemoryLib.h>
#include <Library/CacheMaintenanceLib.h>
#include <Library/DebugLib.h>
#include <Library/DevicePathLib.h>
#include <Library/IoLib.h>
#include <Library/MemoryAllocationLib.h>
#include <Library/PrintLib.h>
#include <Library/SerialPortLib.h>
#include <Library/TimerLib.h>
#include <Library/UefiBootManagerLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiLib.h>

#include <Protocol/DevicePathFromText.h>
#include <Protocol/EmbeddedGpio.h>
#include <Protocol/LoadedImage.h>
#include <Protocol/PlatformBootManager.h>
#include <Protocol/PlatformVirtualKeyboard.h>

#include "HiKey960Dxe.h"

STATIC EMBEDDED_GPIO   *mGpio;

STATIC
VOID
InitSdCard (
  IN VOID
  )
{
  UINT32        Data;

  //
  // LDO16
  // 000: 1.75V, 001: 1.8V, 010: 2.4V, 011: 2.6V, 100: 2.7V,
  // 101: 2.85V, 110: 2.95V, 111: 3.0V.
  //
  Data = MmioRead32 (PMIC_LDO16_VSET_REG) & LDO16_VSET_MASK;
  Data |= 6;
  MmioWrite32 (PMIC_LDO16_VSET_REG, Data);
  MmioOr32 (PMIC_LDO16_ONOFF_ECO_REG, LDO16_ONOFF_ECO_LDO16_ENABLE);
  //
  // wait regulator stable
  //
  MicroSecondDelay (100);

  //
  // LDO9
  // 000: 1.75V, 001: 1.8V, 010: 1.825V, 011: 2.8V, 100: 2.85V,
  // 101: 2.95V, 110: 3.0V, 111: 3.3V.
  //
  Data = MmioRead32 (PMIC_LDO9_VSET_REG) & LDO9_VSET_MASK;
  Data |= 5;
  MmioWrite32 (PMIC_LDO9_VSET_REG, Data);
  MmioOr32 (PMU_REG_BASE + (0x6a << 2), 2);
  //
  // wait regulator stable
  //
  MicroSecondDelay (100);

  //
  // GPIO203
  //
  MmioWrite32 (IOMG_AO_REG_BASE + (24 << 2), 0); // GPIO function

  //
  // SD pinmux
  //
  MmioWrite32 (IOMG_MMC0_000_REG, IOMG_FUNC1); // SD_CLK
  MmioWrite32 (IOMG_MMC0_001_REG, IOMG_FUNC1); // SD_CMD
  MmioWrite32 (IOMG_MMC0_002_REG, IOMG_FUNC1); // SD_DATA0
  MmioWrite32 (IOMG_MMC0_003_REG, IOMG_FUNC1); // SD_DATA1
  MmioWrite32 (IOMG_MMC0_004_REG, IOMG_FUNC1); // SD_DATA2
  MmioWrite32 (IOMG_MMC0_005_REG, IOMG_FUNC1); // SD_DATA3
  MmioWrite32 (IOCG_MMC0_000_REG, IOCG_DRIVE (15)); // SD_CLK float with 32mA
  MmioWrite32 (IOCG_MMC0_001_REG, IOCG_PULLUP | IOCG_DRIVE (8)); // SD_CMD
  MmioWrite32 (IOCG_MMC0_002_REG, IOCG_PULLUP | IOCG_DRIVE (8)); // SD_DATA0
  MmioWrite32 (IOCG_MMC0_003_REG, IOCG_PULLUP | IOCG_DRIVE (8)); // SD_DATA1
  MmioWrite32 (IOCG_MMC0_004_REG, IOCG_PULLUP | IOCG_DRIVE (8)); // SD_DATA2
  MmioWrite32 (IOCG_MMC0_005_REG, IOCG_PULLUP | IOCG_DRIVE (8)); // SD_DATA3

  //
  // SC_SEL_SD:
  //   0xx: 3.2MHz, 100: PPLL0, 101: PPLL1, 11x: PPLL2.
  // SC_DIV_SD:
  //   divider = value + 1
  //
  do {
    MmioOr32 (
      CRG_CLKDIV4,
      CLKDIV4_SC_SEL_SD (7) |
      (CLKDIV4_SC_SEL_SD_MASK << CLKDIV4_SC_MASK_SHIFT)
      );
    Data = MmioRead32 (CRG_CLKDIV4) & CLKDIV4_SC_SEL_SD_MASK;
  } while (Data != CLKDIV4_SC_SEL_SD (7));

  //
  // Unreset SD controller
  //
  MmioWrite32 (CRG_PERRSTDIS4, PERRSTEN4_SD);
  do {
    Data = MmioRead32 (CRG_PERRSTSTAT4);
  } while ((Data & PERRSTEN4_SD) == PERRSTEN4_SD);
  //
  // Enable SD controller clock
  //
  MmioOr32 (CRG_PEREN0, PEREN0_GT_HCLK_SD);
  MmioOr32 (CRG_PEREN4, PEREN4_GT_CLK_SD);
  do {
    Data = MmioRead32 (CRG_PERCLKEN4);
  } while ((Data & PEREN4_GT_CLK_SD) != PEREN4_GT_CLK_SD);
}

VOID
InitPeripherals (
  IN VOID
  )
{
  //
  // Enable FPLL0
  //
  MmioOr32 (SCTRL_SCFPLLCTRL0, SCTRL_SCFPLLCTRL0_FPLL0_EN);

  InitSdCard ();

  //
  // Enable wifi clock
  //
  MmioOr32 (PMIC_HARDWARE_CTRL0, PMIC_HARDWARE_CTRL0_WIFI_CLK);
  MmioOr32 (PMIC_OSC32K_ONOFF_CTRL, PMIC_OSC32K_ONOFF_CTRL_EN_32K);
}

/**
  Notification function of the event defined as belonging to the
  EFI_END_OF_DXE_EVENT_GROUP_GUID event group that was created in
  the entry point of the driver.

  This function is called when an event belonging to the
  EFI_END_OF_DXE_EVENT_GROUP_GUID event group is signalled. Such an
  event is signalled once at the end of the dispatching of all
  drivers (end of the so called DXE phase).

  @param[in]  Event    Event declared in the entry point of the driver whose
                       notification function is being invoked.
  @param[in]  Context  NULL
**/
STATIC
VOID
OnEndOfDxe (
  IN EFI_EVENT  Event,
  IN VOID       *Context
  )
{
  UINT32        BootMode;
  VOID          *RecoveryStr;
  VOID          *SwitchStr;

  BootMode = MmioRead32 (SCTRL_BAK_DATA0) & BOOT_MODE_MASK;
  if (BootMode == BOOT_MODE_RECOVERY) {
    RecoveryStr = "WARNING: CAN NOT BOOT KERNEL IN RECOVERY MODE!\r\n";
    SwitchStr = "Switch to normal boot mode, then reboot to boot kernel.\r\n";
    SerialPortWrite (RecoveryStr, AsciiStrLen (RecoveryStr));
    SerialPortWrite (SwitchStr, AsciiStrLen (SwitchStr));
  }
}

STATIC
EFI_STATUS
CreatePlatformBootOptionFromPath (
  IN     CHAR16                          *PathStr,
  IN     CHAR16                          *Description,
  IN OUT EFI_BOOT_MANAGER_LOAD_OPTION    *BootOption
  )
{
  EFI_STATUS                   Status;
  EFI_DEVICE_PATH              *DevicePath;

  DevicePath = (EFI_DEVICE_PATH *)ConvertTextToDevicePath (PathStr);
  ASSERT (DevicePath != NULL);
  Status = EfiBootManagerInitializeLoadOption (
             BootOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypeBoot,
             LOAD_OPTION_ACTIVE,
             Description,
             DevicePath,
             NULL,
             0
             );
  FreePool (DevicePath);
  return Status;
}

STATIC
EFI_STATUS
CreatePlatformBootOptionFromGuid (
  IN     EFI_GUID                        *FileGuid,
  IN     CHAR16                          *Description,
  IN OUT EFI_BOOT_MANAGER_LOAD_OPTION    *BootOption
  )
{
  EFI_STATUS                             Status;
  EFI_DEVICE_PATH                        *DevicePath;
  EFI_DEVICE_PATH                        *TempDevicePath;
  EFI_LOADED_IMAGE_PROTOCOL              *LoadedImage;
  MEDIA_FW_VOL_FILEPATH_DEVICE_PATH      FileNode;

  Status = gBS->HandleProtocol (
                  gImageHandle,
                  &gEfiLoadedImageProtocolGuid,
                  (VOID **) &LoadedImage
                  );
  ASSERT_EFI_ERROR (Status);
  EfiInitializeFwVolDevicepathNode (&FileNode, FileGuid);
  TempDevicePath = DevicePathFromHandle (LoadedImage->DeviceHandle);
  ASSERT (TempDevicePath != NULL);
  DevicePath = AppendDevicePathNode (
                 TempDevicePath,
                 (EFI_DEVICE_PATH_PROTOCOL *) &FileNode
                 );
  ASSERT (DevicePath != NULL);
  Status = EfiBootManagerInitializeLoadOption (
             BootOption,
             LoadOptionNumberUnassigned,
             LoadOptionTypeBoot,
             LOAD_OPTION_ACTIVE,
             Description,
             DevicePath,
             NULL,
             0
             );
  FreePool (DevicePath);
  return Status;
}

STATIC
EFI_STATUS
GetPlatformBootOptionsAndKeys (
  OUT UINTN                              *BootCount,
  OUT EFI_BOOT_MANAGER_LOAD_OPTION       **BootOptions,
  OUT EFI_INPUT_KEY                      **BootKeys
  )
{
  EFI_GUID                               *FileGuid;
  CHAR16                                 *PathStr;
  EFI_STATUS                             Status;
  UINTN                                  Size;

  Size = sizeof (EFI_BOOT_MANAGER_LOAD_OPTION) * HIKEY960_BOOT_OPTION_NUM;
  *BootOptions = (EFI_BOOT_MANAGER_LOAD_OPTION *)AllocateZeroPool (Size);
  if (*BootOptions == NULL) {
    DEBUG ((DEBUG_ERROR, "Failed to allocate memory for BootOptions\n"));
    return EFI_OUT_OF_RESOURCES;
  }
  Size = sizeof (EFI_INPUT_KEY) * HIKEY960_BOOT_OPTION_NUM;
  *BootKeys = (EFI_INPUT_KEY *)AllocateZeroPool (Size);
  if (*BootKeys == NULL) {
    DEBUG ((DEBUG_ERROR, "Failed to allocate memory for BootKeys\n"));
    Status = EFI_OUT_OF_RESOURCES;
    goto Error;
  }

  PathStr = (CHAR16 *)PcdGetPtr (PcdSdBootDevicePath);
  ASSERT (PathStr != NULL);
  Status = CreatePlatformBootOptionFromPath (
             PathStr,
             L"Boot from SD",
             &(*BootOptions)[0]
             );
  ASSERT_EFI_ERROR (Status);

  PathStr = (CHAR16 *)PcdGetPtr (PcdAndroidBootDevicePath);
  ASSERT (PathStr != NULL);
  Status = CreatePlatformBootOptionFromPath (
             PathStr,
             L"Grub",
             &(*BootOptions)[1]
             );
  ASSERT_EFI_ERROR (Status);

  FileGuid = PcdGetPtr (PcdAndroidBootFile);
  ASSERT (FileGuid != NULL);
  Status = CreatePlatformBootOptionFromGuid (
             FileGuid,
             L"Android Boot",
             &(*BootOptions)[2]
             );
  ASSERT_EFI_ERROR (Status);

  FileGuid = PcdGetPtr (PcdAndroidFastbootFile);
  ASSERT (FileGuid != NULL);
  Status = CreatePlatformBootOptionFromGuid (
             FileGuid,
             L"Android Fastboot",
             &(*BootOptions)[3]
             );
  ASSERT_EFI_ERROR (Status);
  (*BootKeys)[3].ScanCode = SCAN_NULL;
  (*BootKeys)[3].UnicodeChar = 'f';

  *BootCount = 4;

  return EFI_SUCCESS;
Error:
  FreePool (*BootOptions);
  return Status;
}

PLATFORM_BOOT_MANAGER_PROTOCOL mPlatformBootManager = {
  GetPlatformBootOptionsAndKeys
};

EFI_STATUS
EFIAPI
VirtualKeyboardRegister (
  IN VOID
  )
{
  EFI_STATUS           Status;

  Status = gBS->LocateProtocol (
                  &gEmbeddedGpioProtocolGuid,
                  NULL,
                  (VOID **) &mGpio
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }
  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
VirtualKeyboardReset (
  IN VOID
  )
{
  EFI_STATUS           Status;

  if (mGpio == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  //
  // Configure GPIO68 as GPIO function
  //
  MmioWrite32 (0xe896c108, 0);
  Status = mGpio->Set (mGpio, DETECT_SW_FASTBOOT, GPIO_MODE_INPUT);
  return Status;
}

BOOLEAN
EFIAPI
VirtualKeyboardQuery (
  IN VIRTUAL_KBD_KEY             *VirtualKey
  )
{
  EFI_STATUS           Status;
  UINTN                Value = 0;

  if ((VirtualKey == NULL) || (mGpio == NULL)) {
    return FALSE;
  }
  if (MmioRead32 (ADB_REBOOT_ADDRESS) == ADB_REBOOT_BOOTLOADER) {
    goto Done;
  } else {
    Status = mGpio->Get (mGpio, DETECT_SW_FASTBOOT, &Value);
    if (EFI_ERROR (Status) || (Value != 0)) {
      return FALSE;
    }
  }
Done:
  VirtualKey->Signature = VIRTUAL_KEYBOARD_KEY_SIGNATURE;
  VirtualKey->Key.ScanCode = SCAN_NULL;
  VirtualKey->Key.UnicodeChar = L'f';
  return TRUE;
}

EFI_STATUS
EFIAPI
VirtualKeyboardClear (
  IN VIRTUAL_KBD_KEY            *VirtualKey
  )
{
  if (VirtualKey == NULL) {
    return EFI_INVALID_PARAMETER;
  }
  if (MmioRead32 (ADB_REBOOT_ADDRESS) == ADB_REBOOT_BOOTLOADER) {
    MmioWrite32 (ADB_REBOOT_ADDRESS, ADB_REBOOT_NONE);
    WriteBackInvalidateDataCacheRange ((VOID *)ADB_REBOOT_ADDRESS, 4);
  }
  return EFI_SUCCESS;
}

PLATFORM_VIRTUAL_KBD_PROTOCOL mVirtualKeyboard = {
  VirtualKeyboardRegister,
  VirtualKeyboardReset,
  VirtualKeyboardQuery,
  VirtualKeyboardClear
};

EFI_STATUS
EFIAPI
HiKey960EntryPoint (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS            Status;
  EFI_EVENT             EndOfDxeEvent;

  InitPeripherals ();

  //
  // Create an event belonging to the "gEfiEndOfDxeEventGroupGuid" group.
  // The "OnEndOfDxe()" function is declared as the call back function.
  // It will be called at the end of the DXE phase when an event of the
  // same group is signalled to inform about the end of the DXE phase.
  // Install the INSTALL_FDT_PROTOCOL protocol.
  //
  Status = gBS->CreateEventEx (
                  EVT_NOTIFY_SIGNAL,
                  TPL_CALLBACK,
                  OnEndOfDxe,
                  NULL,
                  &gEfiEndOfDxeEventGroupGuid,
                  &EndOfDxeEvent
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->InstallProtocolInterface (
                  &ImageHandle,
                  &gPlatformVirtualKeyboardProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mVirtualKeyboard
                  );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = gBS->InstallProtocolInterface (
                  &ImageHandle,
                  &gPlatformBootManagerProtocolGuid,
                  EFI_NATIVE_INTERFACE,
                  &mPlatformBootManager
                  );
  return Status;
}
