/** @file

 Copyright (c) 2011-2013, ARM Ltd. All rights reserved.<BR>
 SPDX-License-Identifier: BSD-2-Clause-Patent

 **/

#include <PiDxe.h>
#include <Library/BaseMemoryLib.h>
#include <Library/DevicePathLib.h>
#include <Library/UefiBootServicesTableLib.h>
#include <Library/UefiRuntimeServicesTableLib.h>
#include <Library/MemoryAllocationLib.h>

#include <Guid/GlobalVariable.h>

#include "LcdGraphicsOutputDxe.h"

extern BOOLEAN mDisplayInitialized;

//
// Function Definitions
//

STATIC
EFI_STATUS
VideoCopyNoHorizontalOverlap (
  IN UINTN          BitsPerPixel,
  IN volatile VOID  *FrameBufferBase,
  IN UINT32         HorizontalResolution,
  IN UINTN          SourceX,
  IN UINTN          SourceY,
  IN UINTN          DestinationX,
  IN UINTN          DestinationY,
  IN UINTN          Width,
  IN UINTN          Height
)
{
  EFI_STATUS    Status = EFI_SUCCESS;
  UINTN         SourceLine;
  UINTN         DestinationLine;
  UINTN         WidthInBytes;
  UINTN         LineCount;
  INTN          Step;
  VOID          *SourceAddr;
  VOID          *DestinationAddr;

  if( DestinationY <= SourceY ) {
    // scrolling up (or horizontally but without overlap)
    SourceLine       = SourceY;
    DestinationLine  = DestinationY;
    Step             = 1;
  } else {
    // scrolling down
    SourceLine       = SourceY + Height;
    DestinationLine  = DestinationY + Height;
    Step             = -1;
  }

  switch (BitsPerPixel) {

  case LCD_BITS_PER_PIXEL_24:

    WidthInBytes = Width * 4;

    for( LineCount = 0; LineCount < Height; LineCount++ ) {
      // Update the start addresses of source & destination using 32bit pointer arithmetic
      SourceAddr      = (VOID *)((UINT32 *)FrameBufferBase + SourceLine      * HorizontalResolution + SourceX     );
      DestinationAddr = (VOID *)((UINT32 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationX);

      // Copy the entire line Y from video ram to the temp buffer
      CopyMem( DestinationAddr, SourceAddr, WidthInBytes);

      // Update the line numbers
      SourceLine      += Step;
      DestinationLine += Step;
    }
    break;

  case LCD_BITS_PER_PIXEL_16_555:
  case LCD_BITS_PER_PIXEL_16_565:
  case LCD_BITS_PER_PIXEL_12_444:

    WidthInBytes = Width * 2;

    for( LineCount = 0; LineCount < Height; LineCount++ ) {
      // Update the start addresses of source & destination using 16bit pointer arithmetic
      SourceAddr      = (VOID *)((UINT16 *)FrameBufferBase + SourceLine      * HorizontalResolution + SourceX     );
      DestinationAddr = (VOID *)((UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationX);

      // Copy the entire line Y from video ram to the temp buffer
      CopyMem( DestinationAddr, SourceAddr, WidthInBytes);

      // Update the line numbers
      SourceLine      += Step;
      DestinationLine += Step;
    }
    break;

  case LCD_BITS_PER_PIXEL_8:
  case LCD_BITS_PER_PIXEL_4:
  case LCD_BITS_PER_PIXEL_2:
  case LCD_BITS_PER_PIXEL_1:
  default:
    // Can't handle this case
    DEBUG((DEBUG_ERROR, "ArmVeGraphics_Blt: EfiBltVideoToVideo: INVALID Number of Bits Per Pixel: %d\n", BitsPerPixel));
    Status = EFI_INVALID_PARAMETER;
    goto EXIT;
    // break;

  }

  EXIT:
  return Status;
}

STATIC
EFI_STATUS
VideoCopyHorizontalOverlap (
  IN UINTN          BitsPerPixel,
  IN volatile VOID  *FrameBufferBase,
  UINT32            HorizontalResolution,
  IN UINTN          SourceX,
  IN UINTN          SourceY,
  IN UINTN          DestinationX,
  IN UINTN          DestinationY,
  IN UINTN          Width,
  IN UINTN          Height
)
{
  EFI_STATUS      Status = EFI_SUCCESS;

  UINT32 *PixelBuffer32bit;
  UINT32 *SourcePixel32bit;
  UINT32 *DestinationPixel32bit;

  UINT16 *PixelBuffer16bit;
  UINT16 *SourcePixel16bit;
  UINT16 *DestinationPixel16bit;

  UINT32          SourcePixelY;
  UINT32          DestinationPixelY;
  UINTN           SizeIn32Bits;
  UINTN           SizeIn16Bits;

  switch (BitsPerPixel) {

  case LCD_BITS_PER_PIXEL_24:
    // Allocate a temporary buffer

    PixelBuffer32bit = (UINT32 *) AllocatePool((Height * Width) * sizeof(UINT32));

    if (PixelBuffer32bit == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto EXIT;
    }

    SizeIn32Bits = Width * 4;

    // Copy from the video ram (source region) to a temp buffer
    for (SourcePixelY = SourceY, DestinationPixel32bit = PixelBuffer32bit;
         SourcePixelY < SourceY + Height;
         SourcePixelY++, DestinationPixel32bit += Width)
    {
      // Update the start address of line Y (source)
      SourcePixel32bit = (UINT32 *)FrameBufferBase + SourcePixelY * HorizontalResolution + SourceX;

      // Copy the entire line Y from video ram to the temp buffer
      CopyMem( (VOID *)DestinationPixel32bit, (CONST VOID *)SourcePixel32bit, SizeIn32Bits);
    }

    // Copy from the temp buffer to the video ram (destination region)
    for (DestinationPixelY = DestinationY, SourcePixel32bit = PixelBuffer32bit;
         DestinationPixelY < DestinationY + Height;
         DestinationPixelY++, SourcePixel32bit += Width)
    {
      // Update the start address of line Y (target)
      DestinationPixel32bit = (UINT32 *)FrameBufferBase + DestinationPixelY * HorizontalResolution + DestinationX;

      // Copy the entire line Y from the temp buffer to video ram
      CopyMem( (VOID *)DestinationPixel32bit, (CONST VOID *)SourcePixel32bit, SizeIn32Bits);
    }

    // Free up the allocated memory
    FreePool((VOID *) PixelBuffer32bit);

    break;


  case LCD_BITS_PER_PIXEL_16_555:
  case LCD_BITS_PER_PIXEL_16_565:
  case LCD_BITS_PER_PIXEL_12_444:
    // Allocate a temporary buffer
    PixelBuffer16bit = (UINT16 *) AllocatePool((Height * Width) * sizeof(UINT16));

    if (PixelBuffer16bit == NULL) {
      Status = EFI_OUT_OF_RESOURCES;
      goto EXIT;
    }

    // Access each pixel inside the source area of the Video Memory and copy it to the temp buffer

    SizeIn16Bits = Width * 2;

    for (SourcePixelY = SourceY, DestinationPixel16bit = PixelBuffer16bit;
         SourcePixelY < SourceY + Height;
         SourcePixelY++, DestinationPixel16bit += Width)
    {
      // Calculate the source address:
      SourcePixel16bit = (UINT16 *)FrameBufferBase + SourcePixelY * HorizontalResolution + SourceX;

      // Copy the entire line Y from Video to the temp buffer
      CopyMem( (VOID *)DestinationPixel16bit, (CONST VOID *)SourcePixel16bit, SizeIn16Bits);
    }

    // Copy from the temp buffer into the destination area of the Video Memory

    for (DestinationPixelY = DestinationY, SourcePixel16bit = PixelBuffer16bit;
         DestinationPixelY < DestinationY + Height;
         DestinationPixelY++, SourcePixel16bit += Width)
    {
      // Calculate the target address:
      DestinationPixel16bit = (UINT16 *)FrameBufferBase + (DestinationPixelY * HorizontalResolution + DestinationX);

      // Copy the entire line Y from the temp buffer to Video
      CopyMem( (VOID *)DestinationPixel16bit, (CONST VOID *)SourcePixel16bit, SizeIn16Bits);
    }

    // Free the allocated memory
    FreePool((VOID *) PixelBuffer16bit);

    break;


  case LCD_BITS_PER_PIXEL_8:
  case LCD_BITS_PER_PIXEL_4:
  case LCD_BITS_PER_PIXEL_2:
  case LCD_BITS_PER_PIXEL_1:
  default:
    // Can't handle this case
    DEBUG((DEBUG_ERROR, "ArmVeGraphics_Blt: EfiBltVideoToVideo: INVALID Number of Bits Per Pixel: %d\n", BitsPerPixel));
    Status = EFI_INVALID_PARAMETER;
    goto EXIT;
    // break;

  }

EXIT:
  return Status;
}

STATIC
EFI_STATUS
BltVideoFill (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL        *This,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *EfiSourcePixel,     OPTIONAL
  IN UINTN                               SourceX,
  IN UINTN                               SourceY,
  IN UINTN                               DestinationX,
  IN UINTN                               DestinationY,
  IN UINTN                               Width,
  IN UINTN                               Height,
  IN UINTN                               Delta           OPTIONAL   // Number of BYTES in a row of the BltBuffer
  )
{
  EFI_PIXEL_BITMASK*  PixelInformation;
  EFI_STATUS         Status;
  UINT32             HorizontalResolution;
  LCD_BPP            BitsPerPixel;
  VOID            *FrameBufferBase;
  VOID            *DestinationAddr;
  UINT16          *DestinationPixel16bit;
  UINT16          Pixel16bit;
  UINT32          DestinationPixelX;
  UINT32          DestinationLine;
  UINTN           WidthInBytes;

  Status           = EFI_SUCCESS;
  PixelInformation = &This->Mode->Info->PixelInformation;
  FrameBufferBase = (UINTN *)((UINTN)(This->Mode->FrameBufferBase));
  HorizontalResolution = This->Mode->Info->HorizontalResolution;

  LcdPlatformGetBpp (This->Mode->Mode,&BitsPerPixel);

  switch (BitsPerPixel) {
  case LCD_BITS_PER_PIXEL_24:
    WidthInBytes = Width * 4;

    // Copy the SourcePixel into every pixel inside the target rectangle
    for (DestinationLine = DestinationY;
         DestinationLine < DestinationY + Height;
         DestinationLine++)
    {
      // Calculate the target address using 32bit pointer arithmetic:
      DestinationAddr = (VOID *)((UINT32 *)FrameBufferBase + DestinationLine * HorizontalResolution  + DestinationX);

      // Fill the entire line
      SetMem32 (DestinationAddr, WidthInBytes, *((UINT32 *)EfiSourcePixel));
    }
    break;

  case LCD_BITS_PER_PIXEL_16_555:
    // Convert the EFI pixel at the start of the BltBuffer(0,0) into a video display pixel
    Pixel16bit = (UINT16) (
        ( (EfiSourcePixel->Red      <<  7) & PixelInformation->RedMask      )
      | ( (EfiSourcePixel->Green    <<  2) & PixelInformation->GreenMask    )
      | ( (EfiSourcePixel->Blue     >>  3) & PixelInformation->BlueMask     )
//      | ( 0                           & PixelInformation->ReservedMask )
     );

    // Copy the SourcePixel into every pixel inside the target rectangle
    for (DestinationLine = DestinationY;
         DestinationLine < DestinationY + Height;
         DestinationLine++)
    {
      for (DestinationPixelX = DestinationX;
           DestinationPixelX < DestinationX + Width;
           DestinationPixelX++)
      {
        // Calculate the target address:
        DestinationPixel16bit =  (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationPixelX;

        // Copy the pixel into the new target
        *DestinationPixel16bit = Pixel16bit;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_16_565:
    // Convert the EFI pixel at the start of the BltBuffer(0,0) into a video display pixel
    Pixel16bit = (UINT16) (
        ( (EfiSourcePixel->Red      <<  8) & PixelInformation->RedMask      )
      | ( (EfiSourcePixel->Green    <<  3) & PixelInformation->GreenMask    )
      | ( (EfiSourcePixel->Blue     >>  3) & PixelInformation->BlueMask     )
     );

    // Copy the SourcePixel into every pixel inside the target rectangle
    for (DestinationLine = DestinationY;
         DestinationLine < DestinationY + Height;
         DestinationLine++)
    {
      for (DestinationPixelX = DestinationX;
           DestinationPixelX < DestinationX + Width;
           DestinationPixelX++)
      {
        // Calculate the target address:
        DestinationPixel16bit =  (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution  + DestinationPixelX;

        // Copy the pixel into the new target
        *DestinationPixel16bit = Pixel16bit;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_12_444:
    // Convert the EFI pixel at the start of the BltBuffer(0,0) into a video display pixel
    Pixel16bit = (UINT16) (
        ( (EfiSourcePixel->Red      >> 4) & PixelInformation->RedMask      )
      | ( (EfiSourcePixel->Green        ) & PixelInformation->GreenMask    )
      | ( (EfiSourcePixel->Blue     << 4) & PixelInformation->BlueMask     )
     );

    // Copy the SourcePixel into every pixel inside the target rectangle
    for (DestinationLine = DestinationY;
         DestinationLine < DestinationY + Height;
         DestinationLine++)
    {
      for (DestinationPixelX = DestinationX;
           DestinationPixelX < DestinationX + Width;
           DestinationPixelX++)
      {
        // Calculate the target address:
        DestinationPixel16bit =  (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution  + DestinationPixelX;

        // Copy the pixel into the new target
        *DestinationPixel16bit = Pixel16bit;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_8:
  case LCD_BITS_PER_PIXEL_4:
  case LCD_BITS_PER_PIXEL_2:
  case LCD_BITS_PER_PIXEL_1:
  default:
    // Can't handle this case
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: EfiBltVideoFill: INVALID Number of Bits Per Pixel: %d\n", BitsPerPixel));
    Status = EFI_INVALID_PARAMETER;
    break;
  }

  return Status;
}

STATIC
EFI_STATUS
BltVideoToBltBuffer (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL        *This,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *BltBuffer,     OPTIONAL
  IN UINTN                               SourceX,
  IN UINTN                               SourceY,
  IN UINTN                               DestinationX,
  IN UINTN                               DestinationY,
  IN UINTN                               Width,
  IN UINTN                               Height,
  IN UINTN                               Delta           OPTIONAL   // Number of BYTES in a row of the BltBuffer
  )
{
  EFI_STATUS         Status;
  UINT32             HorizontalResolution;
  LCD_BPP            BitsPerPixel;
  EFI_PIXEL_BITMASK  *PixelInformation;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL *EfiDestinationPixel;
  VOID   *FrameBufferBase;
  VOID            *SourceAddr;
  VOID            *DestinationAddr;
  UINT16 *SourcePixel16bit;
  UINT16          Pixel16bit;
  UINT32          SourcePixelX;
  UINT32          SourceLine;
  UINT32          DestinationPixelX;
  UINT32          DestinationLine;
  UINT32          BltBufferHorizontalResolution;
  UINTN           WidthInBytes;

  Status = EFI_SUCCESS;
  PixelInformation = &This->Mode->Info->PixelInformation;
  HorizontalResolution = This->Mode->Info->HorizontalResolution;
  FrameBufferBase = (UINTN *)((UINTN)(This->Mode->FrameBufferBase));

  if(( Delta != 0 ) && ( Delta != Width * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL))) {
    // Delta is not zero and it is different from the width.
    // Divide it by the size of a pixel to find out the buffer's horizontal resolution.
    BltBufferHorizontalResolution = (UINT32) (Delta / sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
  } else {
    BltBufferHorizontalResolution = Width;
  }

  LcdPlatformGetBpp (This->Mode->Mode,&BitsPerPixel);

  switch (BitsPerPixel) {
  case LCD_BITS_PER_PIXEL_24:
    WidthInBytes = Width * 4;

    // Access each line inside the Video Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++)
    {
      // Calculate the source and target addresses using 32bit pointer arithmetic:
      SourceAddr      = (VOID *)((UINT32 *)FrameBufferBase + SourceLine      * HorizontalResolution          + SourceX     );
      DestinationAddr = (VOID *)((UINT32 *)BltBuffer       + DestinationLine * BltBufferHorizontalResolution + DestinationX);

      // Copy the entire line
      CopyMem( DestinationAddr, SourceAddr, WidthInBytes);
    }
    break;

  case LCD_BITS_PER_PIXEL_16_555:
    // Access each pixel inside the Video Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++)
    {
      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        SourcePixel16bit = (UINT16 *)FrameBufferBase + SourceLine * HorizontalResolution + SourcePixelX;
        EfiDestinationPixel = BltBuffer + DestinationLine * BltBufferHorizontalResolution + DestinationPixelX;

        // Snapshot the pixel from the video buffer once, to speed up the operation.
        // If we were dereferencing the pointer, as it is volatile, we would perform 3 memory read operations.
        Pixel16bit = *SourcePixel16bit;

        // Copy the pixel into the new target
        EfiDestinationPixel->Red      = (UINT8) ( (Pixel16bit & PixelInformation->RedMask     ) >>  7 );
        EfiDestinationPixel->Green    = (UINT8) ( (Pixel16bit & PixelInformation->GreenMask   ) >>  2);
        EfiDestinationPixel->Blue     = (UINT8) ( (Pixel16bit & PixelInformation->BlueMask    ) <<  3 );
        // EfiDestinationPixel->Reserved = (UINT8) 0;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_16_565:
    // Access each pixel inside the Video Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++)
    {
      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        SourcePixel16bit = (UINT16 *)FrameBufferBase + SourceLine * HorizontalResolution + SourcePixelX;
        EfiDestinationPixel = BltBuffer + DestinationLine * BltBufferHorizontalResolution + DestinationPixelX;

        // Snapshot the pixel from the video buffer once, to speed up the operation.
        // If we were dereferencing the pointer, as it is volatile, we would perform 3 memory read operations.
        Pixel16bit = *SourcePixel16bit;

        // Copy the pixel into the new target
        // There is no info for the Reserved byte, so we set it to zero
        EfiDestinationPixel->Red      = (UINT8) ( (Pixel16bit & PixelInformation->RedMask     ) >> 8 );
        EfiDestinationPixel->Green    = (UINT8) ( (Pixel16bit & PixelInformation->GreenMask   ) >> 3);
        EfiDestinationPixel->Blue     = (UINT8) ( (Pixel16bit & PixelInformation->BlueMask    ) << 3 );
        // EfiDestinationPixel->Reserved = (UINT8) 0;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_12_444:
    // Access each pixel inside the Video Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++)
    {
      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        SourcePixel16bit = (UINT16 *)FrameBufferBase + SourceLine * HorizontalResolution + SourcePixelX;
        EfiDestinationPixel = BltBuffer + DestinationLine * BltBufferHorizontalResolution + DestinationPixelX;

        // Snapshot the pixel from the video buffer once, to speed up the operation.
        // If we were dereferencing the pointer, as it is volatile, we would perform 3 memory read operations.
        Pixel16bit = *SourcePixel16bit;

        // Copy the pixel into the new target
        EfiDestinationPixel->Red      = (UINT8) ( (Pixel16bit & PixelInformation->RedMask     ) >> 4 );
        EfiDestinationPixel->Green    = (UINT8) ( (Pixel16bit & PixelInformation->GreenMask   )     );
        EfiDestinationPixel->Blue     = (UINT8) ( (Pixel16bit & PixelInformation->BlueMask    ) << 4 );
        // EfiDestinationPixel->Reserved = (UINT8) 0;
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_8:
  case LCD_BITS_PER_PIXEL_4:
  case LCD_BITS_PER_PIXEL_2:
  case LCD_BITS_PER_PIXEL_1:
  default:
    // Can't handle this case
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: EfiBltVideoToBltBuffer: INVALID Number of Bits Per Pixel: %d\n", BitsPerPixel));
    Status = EFI_INVALID_PARAMETER;
    break;
  }
  return Status;
}

STATIC
EFI_STATUS
BltBufferToVideo (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL        *This,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *BltBuffer,     OPTIONAL
  IN UINTN                               SourceX,
  IN UINTN                               SourceY,
  IN UINTN                               DestinationX,
  IN UINTN                               DestinationY,
  IN UINTN                               Width,
  IN UINTN                               Height,
  IN UINTN                               Delta           OPTIONAL   // Number of BYTES in a row of the BltBuffer
  )
{
  EFI_STATUS         Status;
  UINT32             HorizontalResolution;
  LCD_BPP            BitsPerPixel;
  EFI_PIXEL_BITMASK  *PixelInformation;
  EFI_GRAPHICS_OUTPUT_BLT_PIXEL *EfiSourcePixel;
  VOID   *FrameBufferBase;
  VOID            *SourceAddr;
  VOID            *DestinationAddr;
  UINT16 *DestinationPixel16bit;
  UINT32          SourcePixelX;
  UINT32          SourceLine;
  UINT32          DestinationPixelX;
  UINT32          DestinationLine;
  UINT32          BltBufferHorizontalResolution;
  UINTN           WidthInBytes;

  Status = EFI_SUCCESS;
  PixelInformation = &This->Mode->Info->PixelInformation;
  HorizontalResolution = This->Mode->Info->HorizontalResolution;
  FrameBufferBase = (UINTN *)((UINTN)(This->Mode->FrameBufferBase));

  if(( Delta != 0 ) && ( Delta != Width * sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL))) {
    // Delta is not zero and it is different from the width.
    // Divide it by the size of a pixel to find out the buffer's horizontal resolution.
    BltBufferHorizontalResolution = (UINT32) (Delta / sizeof(EFI_GRAPHICS_OUTPUT_BLT_PIXEL));
  } else {
    BltBufferHorizontalResolution = Width;
  }

  LcdPlatformGetBpp (This->Mode->Mode,&BitsPerPixel);

  switch (BitsPerPixel) {
  case LCD_BITS_PER_PIXEL_24:
    WidthInBytes = Width * 4;

    // Access each pixel inside the BltBuffer Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
       SourceLine < SourceY + Height;
       SourceLine++, DestinationLine++)
    {
      // Calculate the source and target addresses using 32bit pointer arithmetic:
      SourceAddr      = (VOID *)((UINT32 *)BltBuffer       + SourceLine      * BltBufferHorizontalResolution + SourceX     );
      DestinationAddr = (VOID *)((UINT32 *)FrameBufferBase + DestinationLine * HorizontalResolution          + DestinationX);

      // Copy the entire row Y
      CopyMem( DestinationAddr, SourceAddr, WidthInBytes);
    }
    break;

  case LCD_BITS_PER_PIXEL_16_555:
    // Access each pixel inside the BltBuffer Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
       SourceLine < SourceY + Height;
       SourceLine++, DestinationLine++) {

      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        EfiSourcePixel  = BltBuffer + SourceLine * BltBufferHorizontalResolution + SourcePixelX;
        DestinationPixel16bit = (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationPixelX;

        // Copy the pixel into the new target
        // Only the most significant bits will be copied across:
        // To convert from 8 bits to 5 bits per pixel we throw away the 3 least significant bits
        *DestinationPixel16bit = (UINT16) (
              ( (EfiSourcePixel->Red      <<  7) & PixelInformation->RedMask      )
            | ( (EfiSourcePixel->Green    <<  2) & PixelInformation->GreenMask    )
            | ( (EfiSourcePixel->Blue     >>  3) & PixelInformation->BlueMask     )
      //            | ( 0                                & PixelInformation->ReservedMask )
            );
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_16_565:
    // Access each pixel inside the BltBuffer Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++) {

      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        EfiSourcePixel = BltBuffer + SourceLine * BltBufferHorizontalResolution + SourcePixelX;
        DestinationPixel16bit = (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationPixelX;

        // Copy the pixel into the new target
        // Only the most significant bits will be copied across:
        // To convert from 8 bits to 5 or 6 bits per pixel we throw away the 3 or 2  least significant bits
        // There is no room for the Reserved byte so we ignore that completely
        *DestinationPixel16bit = (UINT16) (
              ( (EfiSourcePixel->Red      <<  8) & PixelInformation->RedMask      )
            | ( (EfiSourcePixel->Green    <<  3) & PixelInformation->GreenMask    )
            | ( (EfiSourcePixel->Blue     >>  3) & PixelInformation->BlueMask     )
           );
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_12_444:
    // Access each pixel inside the BltBuffer Memory
    for (SourceLine = SourceY, DestinationLine = DestinationY;
         SourceLine < SourceY + Height;
         SourceLine++, DestinationLine++) {

      for (SourcePixelX = SourceX, DestinationPixelX = DestinationX;
           SourcePixelX < SourceX + Width;
           SourcePixelX++, DestinationPixelX++)
      {
        // Calculate the source and target addresses:
        EfiSourcePixel = BltBuffer + SourceLine * BltBufferHorizontalResolution + SourcePixelX;
        DestinationPixel16bit = (UINT16 *)FrameBufferBase + DestinationLine * HorizontalResolution + DestinationPixelX;

        // Copy the pixel into the new target
        // Only the most significant bits will be copied across:
        // To convert from 8 bits to 5 bits per pixel we throw away the 3 least significant bits
        *DestinationPixel16bit = (UINT16) (
              ( (EfiSourcePixel->Red      << 4) & PixelInformation->RedMask      )
            | ( (EfiSourcePixel->Green        ) & PixelInformation->GreenMask    )
            | ( (EfiSourcePixel->Blue     >> 4) & PixelInformation->BlueMask     )
  //            | ( 0                               & PixelInformation->ReservedMask )
           );
      }
    }
    break;

  case LCD_BITS_PER_PIXEL_8:
  case LCD_BITS_PER_PIXEL_4:
  case LCD_BITS_PER_PIXEL_2:
  case LCD_BITS_PER_PIXEL_1:
  default:
    // Can't handle this case
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: EfiBltBufferToVideo: INVALID Number of Bits Per Pixel: %d\n", BitsPerPixel));
    Status = EFI_INVALID_PARAMETER;
    break;
  }
  return Status;
}

STATIC
EFI_STATUS
BltVideoToVideo (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL        *This,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *BltBuffer,     OPTIONAL
  IN UINTN                               SourceX,
  IN UINTN                               SourceY,
  IN UINTN                               DestinationX,
  IN UINTN                               DestinationY,
  IN UINTN                               Width,
  IN UINTN                               Height,
  IN UINTN                               Delta           OPTIONAL   // Number of BYTES in a row of the BltBuffer
  )
{
  EFI_STATUS         Status;
  UINT32             HorizontalResolution;
  LCD_BPP            BitsPerPixel;
  VOID   *FrameBufferBase;

  HorizontalResolution = This->Mode->Info->HorizontalResolution;
  FrameBufferBase = (UINTN *)((UINTN)(This->Mode->FrameBufferBase));

  //
  // BltVideo to BltVideo:
  //
  //  Source is the Video Memory,
  //  Destination is the Video Memory

  LcdPlatformGetBpp (This->Mode->Mode,&BitsPerPixel);
  FrameBufferBase = (UINTN *)((UINTN)(This->Mode->FrameBufferBase));

  // The UEFI spec currently states:
  // "There is no limitation on the overlapping of the source and destination rectangles"
  // Therefore, we must be careful to avoid overwriting the source data
  if( SourceY == DestinationY ) {
    // Copying within the same height, e.g. horizontal shift
    if( SourceX == DestinationX ) {
      // Nothing to do
      Status = EFI_SUCCESS;
    } else if( ((SourceX>DestinationX)?(SourceX - DestinationX):(DestinationX - SourceX)) < Width ) {
      // There is overlap
      Status = VideoCopyHorizontalOverlap (BitsPerPixel, FrameBufferBase, HorizontalResolution, SourceX, SourceY, DestinationX, DestinationY, Width, Height );
    } else {
      // No overlap
      Status = VideoCopyNoHorizontalOverlap (BitsPerPixel, FrameBufferBase, HorizontalResolution, SourceX, SourceY, DestinationX, DestinationY, Width, Height );
    }
  } else {
    // Copying from different heights
    Status = VideoCopyNoHorizontalOverlap (BitsPerPixel, FrameBufferBase, HorizontalResolution, SourceX, SourceY, DestinationX, DestinationY, Width, Height );
  }

  return Status;
}

/***************************************
 * GraphicsOutput Protocol function, mapping to
 * EFI_GRAPHICS_OUTPUT_PROTOCOL.Blt
 *
 * PRESUMES: 1 pixel = 4 bytes (32bits)
 *  ***************************************/
EFI_STATUS
EFIAPI
LcdGraphicsBlt (
  IN EFI_GRAPHICS_OUTPUT_PROTOCOL        *This,
  IN OUT EFI_GRAPHICS_OUTPUT_BLT_PIXEL   *BltBuffer,     OPTIONAL
  IN EFI_GRAPHICS_OUTPUT_BLT_OPERATION   BltOperation,
  IN UINTN                               SourceX,
  IN UINTN                               SourceY,
  IN UINTN                               DestinationX,
  IN UINTN                               DestinationY,
  IN UINTN                               Width,
  IN UINTN                               Height,
  IN UINTN                               Delta           OPTIONAL   // Number of BYTES in a row of the BltBuffer
  )
{
  EFI_STATUS         Status;
  UINT32             HorizontalResolution;
  UINT32             VerticalResolution;
  LCD_INSTANCE*      Instance;

  Instance = LCD_INSTANCE_FROM_GOP_THIS(This);

  // Setup the hardware if not already done
  if (!mDisplayInitialized) {
    Status = InitializeDisplay (Instance);
    if (EFI_ERROR(Status)) {
      goto EXIT;
    }
  }

  HorizontalResolution = This->Mode->Info->HorizontalResolution;
  VerticalResolution   = This->Mode->Info->VerticalResolution;

  DEBUG((DEBUG_INFO, "LcdGraphicsBlt (BltOperation:%d,DestX:%d,DestY:%d,Width:%d,Height:%d) res(%d,%d)\n",
      BltOperation,DestinationX,DestinationY,Width,Height,HorizontalResolution,VerticalResolution));

  // Check we have reasonable parameters
  if (Width == 0 || Height == 0) {
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: ERROR - Invalid dimension: Zero size area.\n" ));
    Status = EFI_INVALID_PARAMETER;
    goto EXIT;
  }

  if ((BltOperation == EfiBltVideoFill) || (BltOperation == EfiBltBufferToVideo) || (BltOperation == EfiBltVideoToBltBuffer)) {
    ASSERT( BltBuffer != NULL);
  }

  /*if ((DestinationX >= HorizontalResolution) || (DestinationY >= VerticalResolution)) {
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: ERROR - Invalid destination.\n" ));
    Status = EFI_INVALID_PARAMETER;
    goto EXIT;
  }*/

  // If we are reading data out of the video buffer, check that the source area is within the display limits
  if ((BltOperation == EfiBltVideoToBltBuffer) || (BltOperation == EfiBltVideoToVideo)) {
    if ((SourceY + Height > VerticalResolution) || (SourceX + Width > HorizontalResolution)) {
      DEBUG((DEBUG_INFO, "LcdGraphicsBlt: ERROR - Invalid source resolution.\n" ));
      DEBUG((DEBUG_INFO, "                      - SourceY=%d + Height=%d > VerticalResolution=%d.\n", SourceY, Height, VerticalResolution ));
      DEBUG((DEBUG_INFO, "                      - SourceX=%d + Width=%d > HorizontalResolution=%d.\n", SourceX, Width, HorizontalResolution ));
      Status = EFI_INVALID_PARAMETER;
      goto EXIT;
    }
  }

  // If we are writing data into the video buffer, that the destination area is within the display limits
  if ((BltOperation == EfiBltVideoFill) || (BltOperation == EfiBltBufferToVideo) || (BltOperation == EfiBltVideoToVideo)) {
    if ((DestinationY + Height > VerticalResolution) || (DestinationX + Width > HorizontalResolution)) {
      DEBUG((DEBUG_INFO, "LcdGraphicsBlt: ERROR - Invalid destination resolution.\n" ));
      DEBUG((DEBUG_INFO, "                      - DestinationY=%d + Height=%d > VerticalResolution=%d.\n", DestinationY, Height, VerticalResolution ));
      DEBUG((DEBUG_INFO, "                      - DestinationX=%d + Width=%d > HorizontalResolution=%d.\n", DestinationX, Width, HorizontalResolution ));
      Status = EFI_INVALID_PARAMETER;
      goto EXIT;
    }
  }

  //
  // Perform the Block Transfer Operation
  //

  switch (BltOperation) {
  case EfiBltVideoFill:
    Status = BltVideoFill (This, BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta);
    break;

  case EfiBltVideoToBltBuffer:
    Status = BltVideoToBltBuffer (This, BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta);
    break;

  case EfiBltBufferToVideo:
    Status = BltBufferToVideo (This, BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta);
    break;

  case EfiBltVideoToVideo:
    Status = BltVideoToVideo (This, BltBuffer, SourceX, SourceY, DestinationX, DestinationY, Width, Height, Delta);
    break;

  case EfiGraphicsOutputBltOperationMax:
  default:
    DEBUG((DEBUG_ERROR, "LcdGraphicsBlt: Invalid Operation\n"));
    Status = EFI_INVALID_PARAMETER;
    break;
  }

EXIT:
  return Status;
}
