/** @file

 Copyright (c) 2011-2014, ARM Ltd. All rights reserved.<BR>
 This program and the accompanying materials
 are licensed and made available under the terms and conditions of the BSD License
 which accompanies this distribution.  The full text of the license may be found at
 http://opensource.org/licenses/bsd-license.php

 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "LcdGraphicsOutputDxe.h"

BOOLEAN mDisplayInitialized = FALSE;

LCD_MODE LcdModes[] = {
  {
    0, 640, 480,
    9, 4,
    96, 16, 48,
    2, 10, 33
  },
  {
    1, 800, 600,
    11, 2,
    120, 56, 64,
    5, 37, 22
  },
  {
    2, 1024, 768,
    6, 2,
    96, 16, 48,
    2, 10, 33
  },
};

LCD_INSTANCE mLcdTemplate = {
  LCD_INSTANCE_SIGNATURE,
  NULL, // Handle
  { // ModeInfo
    0, // Version
    0, // HorizontalResolution
    0, // VerticalResolution
    PixelBltOnly, // PixelFormat
    {
      0xF800, //RedMask;
      0x7E0, //GreenMask;
      0x1F, //BlueMask;
      0x0//ReservedMask
    }, // PixelInformation
    0, // PixelsPerScanLine
  },
  { // Mode
    3, // MaxMode;
    0, // Mode;
    NULL, // Info;
    0, // SizeOfInfo;
    0, // FrameBufferBase;
    0 // FrameBufferSize;
  },
  { // Gop
    LcdGraphicsQueryMode,  // QueryMode
    LcdGraphicsSetMode,    // SetMode
    LcdGraphicsBlt,        // Blt
    NULL                     // *Mode
  },
  { // DevicePath
    {
      {
        HARDWARE_DEVICE_PATH, HW_VENDOR_DP,
        { (UINT8) (sizeof(VENDOR_DEVICE_PATH)), (UINT8) ((sizeof(VENDOR_DEVICE_PATH)) >> 8) },
      },
      // Hardware Device Path for Lcd
      EFI_CALLER_ID_GUID // Use the driver's GUID
    },
    {
      END_DEVICE_PATH_TYPE,
      END_ENTIRE_DEVICE_PATH_SUBTYPE,
      { sizeof(EFI_DEVICE_PATH_PROTOCOL), 0}
    }
  }
};

EFI_STATUS
LcdInstanceContructor (
  OUT LCD_INSTANCE** NewInstance
  )
{
  LCD_INSTANCE* Instance;

  Instance = AllocateCopyPool (sizeof(LCD_INSTANCE), &mLcdTemplate);
  if (Instance == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Instance->Gop.Mode          = &Instance->Mode;
  Instance->Mode.Info         = &Instance->ModeInfo;

  *NewInstance = Instance;
  return EFI_SUCCESS;
}

EFI_STATUS
LcdPlatformGetVram (
  OUT EFI_PHYSICAL_ADDRESS*  VramBaseAddress,
  OUT UINTN*                 VramSize
  )
{
  EFI_STATUS             Status;
  EFI_CPU_ARCH_PROTOCOL  *Cpu;
  UINTN                  MaxSize;

  MaxSize = 0x500000;
  *VramSize = MaxSize;

  // Allocate VRAM from DRAM
  Status = gBS->AllocatePages (AllocateAnyPages, EfiBootServicesData, EFI_SIZE_TO_PAGES((MaxSize)), VramBaseAddress);
  if (EFI_ERROR(Status)) {
    return Status;
  }

  // Ensure the Cpu architectural protocol is already installed
  Status = gBS->LocateProtocol (&gEfiCpuArchProtocolGuid, NULL, (VOID **)&Cpu);
  ASSERT_EFI_ERROR(Status);

  // Mark the VRAM as un-cacheable. The VRAM is inside the DRAM, which is cacheable.
  Status = Cpu->SetMemoryAttributes (Cpu, *VramBaseAddress, *VramSize, EFI_MEMORY_UC);
  if (EFI_ERROR(Status)) {
    gBS->FreePool (VramBaseAddress);
    return Status;
  }

  return EFI_SUCCESS;
}

EFI_STATUS
DssSetMode (
  UINT32 VramBaseAddress,
  UINTN  ModeNumber
  )
{
  // Make sure the interface clock is running
  MmioWrite32 (CM_ICLKEN_DSS, EN_DSS);

  // Stop the functional clocks
  MmioAnd32 (CM_FCLKEN_DSS, ~(EN_DSS1 | EN_DSS2 | EN_TV));

  // Program the DSS clock divisor
  MmioWrite32 (CM_CLKSEL_DSS, 0x1000 | (LcdModes[ModeNumber].DssDivisor));

  // Start the functional clocks
  MmioOr32 (CM_FCLKEN_DSS, (EN_DSS1 | EN_DSS2 | EN_TV));

  // Wait for DSS to stabilize
  gBS->Stall(1);

  // Reset the subsystem
  MmioWrite32(DSS_SYSCONFIG, DSS_SOFTRESET);
  while (!(MmioRead32 (DSS_SYSSTATUS) & DSS_RESETDONE));

  // Configure LCD parameters
  MmioWrite32 (DISPC_SIZE_LCD,
               ((LcdModes[ModeNumber].HorizontalResolution - 1)
               | ((LcdModes[ModeNumber].VerticalResolution - 1) << 16))
              );
  MmioWrite32 (DISPC_TIMING_H,
               ( (LcdModes[ModeNumber].HSync - 1)
               | ((LcdModes[ModeNumber].HFrontPorch - 1) << 8)
               | ((LcdModes[ModeNumber].HBackPorch - 1) << 20))
              );
  MmioWrite32 (DISPC_TIMING_V,
               ( (LcdModes[ModeNumber].VSync - 1)
               | ((LcdModes[ModeNumber].VFrontPorch - 1) << 8)
               | ((LcdModes[ModeNumber].VBackPorch - 1) << 20))
              );

  // Set the framebuffer to only load frames (no gamma tables)
  MmioAnd32 (DISPC_CONFIG, CLEARLOADMODE);
  MmioOr32  (DISPC_CONFIG, LOAD_FRAME_ONLY);

  // Divisor for the pixel clock
  MmioWrite32(DISPC_DIVISOR, ((1 << 16) | LcdModes[ModeNumber].DispcDivisor) );

  // Set up the graphics layer
  MmioWrite32 (DISPC_GFX_PRELD, 0x2D8);
  MmioWrite32 (DISPC_GFX_BA0, VramBaseAddress);
  MmioWrite32 (DISPC_GFX_SIZE,
               ((LcdModes[ModeNumber].HorizontalResolution - 1)
               | ((LcdModes[ModeNumber].VerticalResolution - 1) << 16))
              );

  MmioWrite32(DISPC_GFX_ATTR, (GFXENABLE | RGB16 | BURSTSIZE16));

  // Start it all
  MmioOr32 (DISPC_CONTROL, (LCDENABLE | ACTIVEMATRIX | DATALINES24 | BYPASS_MODE | LCDENABLESIGNAL));
  MmioOr32 (DISPC_CONTROL, GOLCD);

  return EFI_SUCCESS;
}

EFI_STATUS
HwInitializeDisplay (
  UINTN VramBaseAddress,
  UINTN VramSize
  )
{
  EFI_STATUS    Status;
  UINT8         Data;
  EFI_TPL       OldTpl;
  EMBEDDED_EXTERNAL_DEVICE   *gTPS65950;

  // Enable power lines used by TFP410
  Status = gBS->LocateProtocol (&gEmbeddedExternalDeviceProtocolGuid, NULL, (VOID **)&gTPS65950);
  ASSERT_EFI_ERROR (Status);

  OldTpl = gBS->RaiseTPL(TPL_NOTIFY);
  Data = VAUX_DEV_GRP_P1;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VPLL2_DEV_GRP), 1, &Data);
  ASSERT_EFI_ERROR(Status);

  Data = VAUX_DEDICATED_18V;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID4, VPLL2_DEDICATED), 1, &Data);
  ASSERT_EFI_ERROR (Status);

  // Power up TFP410 (set GPIO2 on TPS - for BeagleBoard-xM)
  Status = gTPS65950->Read (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, GPIODATADIR1), 1, &Data);
  ASSERT_EFI_ERROR (Status);
  Data |= BIT2;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, GPIODATADIR1), 1, &Data);
  ASSERT_EFI_ERROR (Status);

  Data = BIT2;
  Status = gTPS65950->Write (gTPS65950, EXTERNAL_DEVICE_REGISTER(I2C_ADDR_GRP_ID2, SETGPIODATAOUT1), 1, &Data);
  ASSERT_EFI_ERROR (Status);

  gBS->RestoreTPL(OldTpl);

  // Power up TFP410 (set GPIO 170 - for older BeagleBoards)
  MmioAnd32 (GPIO6_BASE + GPIO_OE, ~BIT10);
  MmioOr32  (GPIO6_BASE + GPIO_SETDATAOUT, BIT10);

  return EFI_SUCCESS;
}

EFI_STATUS
InitializeDisplay (
  IN LCD_INSTANCE* Instance
  )
{
  EFI_STATUS           Status;
  UINTN                VramSize;
  EFI_PHYSICAL_ADDRESS VramBaseAddress;

  Status = LcdPlatformGetVram (&VramBaseAddress, &VramSize);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Instance->Mode.FrameBufferBase = VramBaseAddress;
  Instance->Mode.FrameBufferSize = VramSize;

  Status = HwInitializeDisplay((UINTN)VramBaseAddress, VramSize);
  if (!EFI_ERROR (Status)) {
    mDisplayInitialized = TRUE;
  }

  return Status;
}

EFI_STATUS
EFIAPI
LcdGraphicsQueryMode (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL            *This,
  IN UINT32                                  ModeNumber,
  OUT UINTN                                  *SizeOfInfo,
  OUT EFI_GRAPHICS_OUTPUT_MODE_INFORMATION   **Info
  )
{
  LCD_INSTANCE  *Instance;

  Instance = LCD_INSTANCE_FROM_GOP_THIS(This);

  if (!mDisplayInitialized) {
    InitializeDisplay (Instance);
  }

  // Error checking
  if ( (This == NULL) || (Info == NULL) || (SizeOfInfo == NULL) || (ModeNumber >= This->Mode->MaxMode) ) {
    DEBUG((DEBUG_ERROR, "LcdGraphicsQueryMode: ERROR - For mode number %d : Invalid Parameter.\n", ModeNumber ));
    return EFI_INVALID_PARAMETER;
  }

  *Info = AllocateCopyPool(sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION), &Instance->ModeInfo);
  if (*Info == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  *SizeOfInfo = sizeof (EFI_GRAPHICS_OUTPUT_MODE_INFORMATION);

  (*Info)->Version = 0;
  (*Info)->HorizontalResolution = LcdModes[ModeNumber].HorizontalResolution;
  (*Info)->VerticalResolution = LcdModes[ModeNumber].VerticalResolution;
  (*Info)->PixelFormat = PixelBltOnly;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
LcdGraphicsSetMode (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL   *This,
  IN UINT32                         ModeNumber
  )
{
  LCD_INSTANCE  *Instance;

  Instance = LCD_INSTANCE_FROM_GOP_THIS(This);

  if (ModeNumber >= Instance->Mode.MaxMode) {
    return EFI_UNSUPPORTED;
  }

  if (!mDisplayInitialized) {
    InitializeDisplay (Instance);
  }

  DssSetMode((UINT32)Instance->Mode.FrameBufferBase, ModeNumber);

  Instance->Mode.Mode = ModeNumber;
  Instance->ModeInfo.HorizontalResolution = LcdModes[ModeNumber].HorizontalResolution;
  Instance->ModeInfo.VerticalResolution = LcdModes[ModeNumber].VerticalResolution;

  return EFI_SUCCESS;
}

EFI_STATUS
EFIAPI
LcdGraphicsOutputDxeInitialize (
  IN EFI_HANDLE         ImageHandle,
  IN EFI_SYSTEM_TABLE   *SystemTable
  )
{
  EFI_STATUS Status = EFI_SUCCESS;
  LCD_INSTANCE* Instance;

  Status = LcdInstanceContructor (&Instance);
  if (EFI_ERROR(Status)) {
    goto EXIT;
  }

  // Install the Graphics Output Protocol and the Device Path
  Status = gBS->InstallMultipleProtocolInterfaces(
             &Instance->Handle,
             &gEfiGraphicsOutputProtocolGuid, &Instance->Gop,
             &gEfiDevicePathProtocolGuid,     &Instance->DevicePath,
             NULL
             );

  if (EFI_ERROR(Status)) {
    DEBUG((DEBUG_ERROR, "GraphicsOutputDxeInitialize: Can not install the protocol. Exit Status=%r\n", Status));
    goto EXIT;
  }

  // Register for an ExitBootServicesEvent
  // When ExitBootServices starts, this function here will make sure that the graphics driver will shut down properly,
  // i.e. it will free up all allocated memory and perform any necessary hardware re-configuration.
  /*Status = gBS->CreateEvent (
               EVT_SIGNAL_EXIT_BOOT_SERVICES,
               TPL_NOTIFY,
               LcdGraphicsExitBootServicesEvent, NULL,
               &Instance->ExitBootServicesEvent
               );

  if (EFI_ERROR(Status)) {
    DEBUG((DEBUG_ERROR, "GraphicsOutputDxeInitialize: Can not install the ExitBootServicesEvent handler. Exit Status=%r\n", Status));
    goto EXIT_ERROR_UNINSTALL_PROTOCOL;
  }*/

  // To get here, everything must be fine, so just exit
  goto EXIT;

//EXIT_ERROR_UNINSTALL_PROTOCOL:
  /* The following function could return an error message,
   * however, to get here something must have gone wrong already,
   * so preserve the original error, i.e. don't change
   * the Status variable, even it fails to uninstall the protocol.
   */
  /*  gBS->UninstallMultipleProtocolInterfaces (
        Instance->Handle,
        &gEfiGraphicsOutputProtocolGuid, &Instance->Gop, // Uninstall Graphics Output protocol
        &gEfiDevicePathProtocolGuid,     &Instance->DevicePath,     // Uninstall device path
        NULL
        );*/

EXIT:
  return Status;

}
